├── IOS ├── Editor │ ├── XUPorter │ │ ├── .gitignore │ │ ├── XCConfigurationList.cs │ │ ├── PBX Editor │ │ │ ├── PBXVariantGroup.cs │ │ │ ├── PBXProject.cs │ │ │ ├── PBXList.cs │ │ │ ├── PBXSortedDictionary.cs │ │ │ ├── PBXGroup.cs │ │ │ ├── PBXBuildPhase.cs │ │ │ ├── PBXDictionary.cs │ │ │ ├── PBXBuildFile.cs │ │ │ ├── PBXObject.cs │ │ │ ├── PBXFileReference.cs │ │ │ └── PBXParser.cs │ │ ├── XCodePostProcess.cs │ │ ├── XCPlist.cs │ │ ├── XCMod.cs │ │ ├── XCBuildConfiguration.cs │ │ ├── MiniJSON │ │ │ └── MiniJSON.cs │ │ ├── XCProject.cs │ │ └── Plist │ │ │ └── Plist.cs │ ├── XUPorter.meta │ ├── DMIosBuilder.cs.meta │ ├── GarbageCodeTool.cs.meta │ ├── DMIosBuilder.cs │ └── GarbageCodeTool.cs └── Editor.meta └── README.md /IOS/Editor/XUPorter/.gitignore: -------------------------------------------------------------------------------- 1 | *.meta -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unityiOSBulid 2 | unity C# iOS打包相关 3 | 包括 4 | * XUPorter简单改造代码 5 | * C#代码混淆类`GarbageCodeTool.cs` 6 | -------------------------------------------------------------------------------- /IOS/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 93b1be8f69ee15a4a838a60360b48efe 3 | folderAsset: yes 4 | timeCreated: 1460111368 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: accb573e018af434f917dbe000527e1e 3 | folderAsset: yes 4 | timeCreated: 1460111368 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /IOS/Editor/DMIosBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 54a9e07d778c97a4f81fb40c7ea16289 3 | timeCreated: 1460111371 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /IOS/Editor/GarbageCodeTool.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 97be3ffd20afe4f0fb4bfc46b1fe8092 3 | timeCreated: 1547100252 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/XCConfigurationList.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR && UNITY_IPHONE 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace UnityEditor.XCodeEditor 9 | { 10 | public class XCConfigurationList : PBXObject 11 | { 12 | // XCBuildConfigurationList buildConfigurations; 13 | // bool defaultConfigurationIsVisible = false; 14 | // string defaultConfigurationName; 15 | 16 | public XCConfigurationList( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 17 | } 18 | } 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXVariantGroup.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR && UNITY_IPHONE 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace UnityEditor.XCodeEditor 9 | { 10 | public class PBXVariantGroup : PBXGroup 11 | { 12 | #region Constructor 13 | 14 | public PBXVariantGroup( string name, string path = null, string tree = "SOURCE_ROOT" ) 15 | : base(name, path, tree) 16 | { 17 | } 18 | 19 | public PBXVariantGroup( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) 20 | { 21 | } 22 | 23 | #endregion 24 | } 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXProject.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR && UNITY_IPHONE 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace UnityEditor.XCodeEditor 9 | { 10 | public class PBXProject : PBXObject 11 | { 12 | protected string MAINGROUP_KEY = "mainGroup"; 13 | protected string KNOWN_REGIONS_KEY = "knownRegions"; 14 | 15 | protected bool _clearedLoc = false; 16 | 17 | public PBXProject() : base() { 18 | } 19 | 20 | public PBXProject( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 21 | } 22 | 23 | public string mainGroupID { 24 | get { 25 | return (string)_data[ MAINGROUP_KEY ]; 26 | } 27 | } 28 | 29 | public PBXList knownRegions { 30 | get { 31 | return (PBXList)_data[ KNOWN_REGIONS_KEY ]; 32 | } 33 | } 34 | 35 | public void AddRegion(string region) { 36 | if (!_clearedLoc) 37 | { 38 | // Only include localizations we explicitly specify 39 | knownRegions.Clear(); 40 | _clearedLoc = true; 41 | } 42 | 43 | knownRegions.Add(region); 44 | } 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXList.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace UnityEditor.XCodeEditor 8 | { 9 | public class PBXList : ArrayList 10 | { 11 | public PBXList() 12 | { 13 | 14 | } 15 | 16 | public PBXList( object firstValue ) 17 | { 18 | this.Add( firstValue ); 19 | } 20 | 21 | /// 22 | /// This allows us to use the form: 23 | /// "if (x)" or "if (!x)" 24 | /// 25 | public static implicit operator bool( PBXList x ) { 26 | //if null or empty, treat us as false/null 27 | return (x == null) ? false : (x.Count == 0); 28 | } 29 | 30 | /// 31 | /// I find this handy. return our fields as comma-separated values 32 | /// 33 | public string ToCSV() { 34 | // TODO use a char sep argument to allow specifying separator 35 | string ret = string.Empty; 36 | foreach (string item in this) { 37 | ret += "\""; 38 | ret += item; 39 | ret += "\", "; 40 | } 41 | return ret; 42 | } 43 | 44 | /// 45 | /// Concatenate and format so appears as "{,,,}" 46 | /// 47 | public override string ToString() { 48 | return "{" + this.ToCSV() + "} "; 49 | } 50 | } 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXSortedDictionary.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR && UNITY_IPHONE 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace UnityEditor.XCodeEditor 9 | { 10 | public class PBXSortedDictionary : SortedDictionary 11 | { 12 | 13 | public void Append( PBXDictionary dictionary ) 14 | { 15 | foreach( var item in dictionary) { 16 | this.Add( item.Key, item.Value ); 17 | } 18 | } 19 | 20 | public void Append( PBXDictionary dictionary ) where T : PBXObject 21 | { 22 | foreach( var item in dictionary) { 23 | this.Add( item.Key, item.Value ); 24 | } 25 | } 26 | } 27 | 28 | public class PBXSortedDictionary : SortedDictionary where T : PBXObject 29 | { 30 | public PBXSortedDictionary() 31 | { 32 | 33 | } 34 | 35 | public PBXSortedDictionary( PBXDictionary genericDictionary ) 36 | { 37 | foreach( KeyValuePair currentItem in genericDictionary ) { 38 | if( ((string)((PBXDictionary)currentItem.Value)[ "isa" ]).CompareTo( typeof(T).Name ) == 0 ) { 39 | T instance = (T)System.Activator.CreateInstance( typeof(T), currentItem.Key, (PBXDictionary)currentItem.Value ); 40 | this.Add( currentItem.Key, instance ); 41 | } 42 | } 43 | } 44 | 45 | public void Add( T newObject ) 46 | { 47 | this.Add( newObject.guid, newObject ); 48 | } 49 | 50 | public void Append( PBXDictionary dictionary ) 51 | { 52 | foreach( KeyValuePair item in dictionary) { 53 | this.Add( item.Key, (T)item.Value ); 54 | } 55 | } 56 | 57 | } 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/XCodePostProcess.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR && UNITY_IPHONE 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | using UnityEditor.Callbacks; 7 | using UnityEditor.XCodeEditor; 8 | using System.IO; 9 | using System.Collections; 10 | using System.Collections.Generic; 11 | 12 | 13 | public static class XCodePostProcess 14 | { 15 | private static void ParseFrameWork( string strPathVal, List< string > listVal ) 16 | { 17 | string[] dirs = System.IO.Directory.GetDirectories( strPathVal ); 18 | 19 | for( int lCnt = 0; lCnt < dirs.Length; ++lCnt ) 20 | { 21 | if( dirs[lCnt].EndsWith( ".framework" ) ) 22 | { 23 | if( ! listVal.Contains( dirs[lCnt] ) ) 24 | { 25 | listVal.Add( dirs[lCnt] ); 26 | } 27 | } 28 | else 29 | { 30 | ParseFrameWork( dirs[lCnt], listVal ); 31 | } 32 | 33 | } 34 | } 35 | 36 | public static string s_projModePath = null; 37 | 38 | 39 | [PostProcessBuild(100)] 40 | public static void OnPostProcessBuild( BuildTarget target, string pathToBuiltProject ) 41 | { 42 | if( string.IsNullOrEmpty( s_projModePath ) ) 43 | { 44 | return; 45 | } 46 | 47 | Debug.Log( "Export Path => " + pathToBuiltProject ); 48 | 49 | if ( target != BuildTarget.iOS ) 50 | { 51 | Debug.LogWarning("Target is not iPhone. XCodePostProcess will not run"); 52 | return; 53 | } 54 | 55 | // Create a new project object from build target 56 | XCProject project = new XCProject( pathToBuiltProject ); 57 | 58 | List< string > ListFrameworks = new List< string >(); 59 | 60 | XCMod mod = new XCMod( s_projModePath ); 61 | 62 | foreach( string folderPath in mod.folders ) 63 | { 64 | ParseFrameWork( folderPath, ListFrameworks ); 65 | } 66 | 67 | for( int lCnt = 0; lCnt < ListFrameworks.Count; ++lCnt ) 68 | { 69 | mod.files.Add( ListFrameworks[lCnt] ); 70 | } 71 | 72 | project.ApplyMod( mod ); 73 | 74 | // project.overwriteBuildSetting("CODE_SIGN_IDENTITY[sdk=iphoneos*]", "iPhone Distribution", "Release"); 75 | 76 | project.Save(); 77 | 78 | s_projModePath = null; 79 | } 80 | 81 | public static void Log(string message) 82 | { 83 | UnityEngine.Debug.Log("PostProcess: "+message); 84 | } 85 | 86 | } 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXGroup.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace UnityEditor.XCodeEditor 8 | { 9 | public class PBXGroup : PBXObject 10 | { 11 | protected const string NAME_KEY = "name"; 12 | protected const string CHILDREN_KEY = "children"; 13 | protected const string PATH_KEY = "path"; 14 | protected const string SOURCETREE_KEY = "sourceTree"; 15 | 16 | #region Constructor 17 | 18 | public PBXGroup( string name, string path = null, string tree = "SOURCE_ROOT" ) : base() 19 | { 20 | this.Add( CHILDREN_KEY, new PBXList() ); 21 | this.Add( NAME_KEY, name ); 22 | 23 | if( path != null ) { 24 | this.Add( PATH_KEY, path ); 25 | this.Add( SOURCETREE_KEY, tree ); 26 | } 27 | else { 28 | this.Add( SOURCETREE_KEY, "" ); 29 | } 30 | } 31 | 32 | public PBXGroup( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) 33 | { 34 | 35 | } 36 | 37 | #endregion 38 | #region Properties 39 | 40 | public PBXList children { 41 | get { 42 | if( !ContainsKey( CHILDREN_KEY ) ) { 43 | this.Add( CHILDREN_KEY, new PBXList() ); 44 | } 45 | return (PBXList)_data[CHILDREN_KEY]; 46 | } 47 | } 48 | 49 | public string name { 50 | get { 51 | if( !ContainsKey( NAME_KEY ) ) { 52 | return null; 53 | } 54 | return (string)_data[NAME_KEY]; 55 | } 56 | } 57 | 58 | public string path { 59 | get { 60 | if( !ContainsKey( PATH_KEY ) ) { 61 | return null; 62 | } 63 | return (string)_data[PATH_KEY]; 64 | } 65 | } 66 | 67 | public string sourceTree { 68 | get { 69 | return (string)_data[SOURCETREE_KEY]; 70 | } 71 | } 72 | 73 | #endregion 74 | 75 | 76 | public string AddChild( PBXObject child ) 77 | { 78 | if( child is PBXFileReference || child is PBXGroup ) { 79 | children.Add( child.guid ); 80 | return child.guid; 81 | } 82 | 83 | return null; 84 | } 85 | 86 | public void RemoveChild( string id ) 87 | { 88 | if( !IsGuid( id ) ) 89 | return; 90 | 91 | children.Remove( id ); 92 | } 93 | 94 | public bool HasChild( string id ) 95 | { 96 | if( !ContainsKey( CHILDREN_KEY ) ) { 97 | this.Add( CHILDREN_KEY, new PBXList() ); 98 | return false; 99 | } 100 | 101 | if( !IsGuid( id ) ) 102 | return false; 103 | 104 | return ((PBXList)_data[ CHILDREN_KEY ]).Contains( id ); 105 | } 106 | 107 | public string GetName() 108 | { 109 | return (string)_data[ NAME_KEY ]; 110 | } 111 | } 112 | } 113 | 114 | #endif 115 | 116 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXBuildPhase.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace UnityEditor.XCodeEditor 8 | { 9 | public class PBXBuildPhase : PBXObject 10 | { 11 | protected const string FILES_KEY = "files"; 12 | 13 | public PBXBuildPhase() :base() 14 | { 15 | } 16 | 17 | public PBXBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 18 | { 19 | } 20 | 21 | public bool AddBuildFile( PBXBuildFile file ) 22 | { 23 | if( !ContainsKey( FILES_KEY ) ){ 24 | this.Add( FILES_KEY, new PBXList() ); 25 | } 26 | ((PBXList)_data[ FILES_KEY ]).Add( file.guid ); 27 | return true; 28 | } 29 | 30 | public void RemoveBuildFile( string id ) 31 | { 32 | if( !ContainsKey( FILES_KEY ) ) { 33 | this.Add( FILES_KEY, new PBXList() ); 34 | return; 35 | } 36 | 37 | ((PBXList)_data[ FILES_KEY ]).Remove( id ); 38 | } 39 | 40 | public bool HasBuildFile( string id ) 41 | { 42 | if( !ContainsKey( FILES_KEY ) ) { 43 | this.Add( FILES_KEY, new PBXList() ); 44 | return false; 45 | } 46 | 47 | if( !IsGuid( id ) ) 48 | return false; 49 | 50 | return ((PBXList)_data[ FILES_KEY ]).Contains( id ); 51 | } 52 | 53 | public PBXList files { 54 | get { 55 | if( !ContainsKey( FILES_KEY ) ) { 56 | this.Add( FILES_KEY, new PBXList() ); 57 | } 58 | return (PBXList)_data[ FILES_KEY ]; 59 | } 60 | } 61 | } 62 | 63 | public class PBXFrameworksBuildPhase : PBXBuildPhase 64 | { 65 | public PBXFrameworksBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 66 | { 67 | } 68 | } 69 | 70 | public class PBXResourcesBuildPhase : PBXBuildPhase 71 | { 72 | public PBXResourcesBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 73 | { 74 | } 75 | } 76 | 77 | public class PBXShellScriptBuildPhase : PBXBuildPhase 78 | { 79 | public PBXShellScriptBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 80 | { 81 | } 82 | } 83 | 84 | public class PBXSourcesBuildPhase : PBXBuildPhase 85 | { 86 | public PBXSourcesBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 87 | { 88 | } 89 | } 90 | 91 | public class PBXCopyFilesBuildPhase : PBXBuildPhase 92 | { 93 | //Embed Frameworks PBXCopyFilesBuildPhase constructor 94 | //to make sure "isa" = "PBXCopyFilesBuildPhase" 95 | public PBXCopyFilesBuildPhase( int buildActionMask ) :base() 96 | { 97 | this.Add("buildActionMask", buildActionMask); 98 | this.Add("dstPath", ""); 99 | this.Add("dstSubfolderSpec", 10); 100 | this.Add("name", "Embed Frameworks"); 101 | this.Add("runOnlyForDeploymentPostprocessing", 0); 102 | } 103 | 104 | public PBXCopyFilesBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 105 | { 106 | } 107 | } 108 | } 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXDictionary.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace UnityEditor.XCodeEditor 8 | { 9 | public class PBXDictionary : Dictionary 10 | { 11 | 12 | public void Append( PBXDictionary dictionary ) 13 | { 14 | foreach( var item in dictionary) { 15 | this.Add( item.Key, item.Value ); 16 | } 17 | } 18 | 19 | public void Append( PBXDictionary dictionary ) where T : PBXObject 20 | { 21 | foreach( var item in dictionary) { 22 | this.Add( item.Key, item.Value ); 23 | } 24 | } 25 | 26 | public void Append( PBXSortedDictionary dictionary ) 27 | { 28 | foreach( var item in dictionary) { 29 | this.Add( item.Key, item.Value ); 30 | } 31 | } 32 | 33 | public void Append( PBXSortedDictionary dictionary ) where T : PBXObject 34 | { 35 | foreach( var item in dictionary) { 36 | this.Add( item.Key, item.Value ); 37 | } 38 | } 39 | 40 | /// 41 | /// This allows us to use the form: 42 | /// "if (x)" or "if (!x)" 43 | /// 44 | public static implicit operator bool( PBXDictionary x ) { 45 | //if null or empty, treat us as false/null 46 | return (x == null) ? false : (x.Count == 0); 47 | } 48 | 49 | /// 50 | /// I find this handy. return our fields as comma-separated values 51 | /// 52 | public string ToCSV() { 53 | // TODO use a char sep argument to allow specifying separator 54 | string ret = string.Empty; 55 | foreach (KeyValuePair item in this) { 56 | ret += "<"; 57 | ret += item.Key; 58 | ret += ", "; 59 | ret += item.Value; 60 | ret += ">, "; 61 | } 62 | return ret; 63 | } 64 | 65 | /// 66 | /// Concatenate and format so appears as "{,,,}" 67 | /// 68 | public override string ToString() { 69 | return "{" + this.ToCSV() + "}"; 70 | } 71 | 72 | } 73 | 74 | public class PBXDictionary : Dictionary where T : PBXObject 75 | { 76 | public PBXDictionary() 77 | { 78 | 79 | } 80 | 81 | public PBXDictionary( PBXDictionary genericDictionary ) 82 | { 83 | foreach( KeyValuePair currentItem in genericDictionary ) { 84 | if( ((string)((PBXDictionary)currentItem.Value)[ "isa" ]).CompareTo( typeof(T).Name ) == 0 ) { 85 | T instance = (T)System.Activator.CreateInstance( typeof(T), currentItem.Key, (PBXDictionary)currentItem.Value ); 86 | this.Add( currentItem.Key, instance ); 87 | } 88 | } 89 | } 90 | 91 | public PBXDictionary( PBXSortedDictionary genericDictionary ) 92 | { 93 | foreach( KeyValuePair currentItem in genericDictionary ) { 94 | if( ((string)((PBXDictionary)currentItem.Value)[ "isa" ]).CompareTo( typeof(T).Name ) == 0 ) { 95 | T instance = (T)System.Activator.CreateInstance( typeof(T), currentItem.Key, (PBXDictionary)currentItem.Value ); 96 | this.Add( currentItem.Key, instance ); 97 | } 98 | } 99 | } 100 | 101 | public void Add( T newObject ) 102 | { 103 | this.Add( newObject.guid, newObject ); 104 | } 105 | 106 | public void Append( PBXDictionary dictionary ) 107 | { 108 | foreach( KeyValuePair item in dictionary) { 109 | this.Add( item.Key, (T)item.Value ); 110 | } 111 | } 112 | 113 | } 114 | } 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXBuildFile.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace UnityEditor.XCodeEditor 8 | { 9 | public class PBXBuildFile : PBXObject 10 | { 11 | private const string FILE_REF_KEY = "fileRef"; 12 | private const string SETTINGS_KEY = "settings"; 13 | private const string ATTRIBUTES_KEY = "ATTRIBUTES"; 14 | private const string WEAK_VALUE = "Weak"; 15 | private const string COMPILER_FLAGS_KEY = "COMPILER_FLAGS"; 16 | 17 | public PBXBuildFile( PBXFileReference fileRef, bool weak = false ) : base() 18 | { 19 | this.Add( FILE_REF_KEY, fileRef.guid ); 20 | SetWeakLink( weak ); 21 | 22 | if (!string.IsNullOrEmpty(fileRef.compilerFlags)) 23 | { 24 | foreach (var flag in fileRef.compilerFlags.Split(',')) 25 | AddCompilerFlag(flag); 26 | } 27 | } 28 | 29 | public PBXBuildFile( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 30 | { 31 | } 32 | 33 | public string fileRef 34 | { 35 | get { 36 | return (string)_data[ FILE_REF_KEY ]; 37 | } 38 | } 39 | 40 | public bool SetWeakLink( bool weak = false ) 41 | { 42 | PBXDictionary settings = null; 43 | PBXList attributes = null; 44 | 45 | if( !_data.ContainsKey( SETTINGS_KEY ) ) { 46 | if( weak ) { 47 | attributes = new PBXList(); 48 | attributes.Add( WEAK_VALUE ); 49 | 50 | settings = new PBXDictionary(); 51 | settings.Add( ATTRIBUTES_KEY, attributes ); 52 | 53 | _data.Add( SETTINGS_KEY, settings ); 54 | } 55 | return true; 56 | } 57 | 58 | settings = _data[ SETTINGS_KEY ] as PBXDictionary; 59 | if( !settings.ContainsKey( ATTRIBUTES_KEY ) ) { 60 | if( weak ) { 61 | attributes = new PBXList(); 62 | attributes.Add( WEAK_VALUE ); 63 | settings.Add( ATTRIBUTES_KEY, attributes ); 64 | return true; 65 | } 66 | else { 67 | return false; 68 | } 69 | } 70 | else { 71 | attributes = settings[ ATTRIBUTES_KEY ] as PBXList; 72 | } 73 | 74 | if( weak ) { 75 | attributes.Add( WEAK_VALUE ); 76 | } 77 | else { 78 | attributes.Remove( WEAK_VALUE ); 79 | } 80 | 81 | settings.Add( ATTRIBUTES_KEY, attributes ); 82 | this.Add( SETTINGS_KEY, settings ); 83 | 84 | return true; 85 | } 86 | 87 | //CodeSignOnCopy 88 | public bool AddCodeSignOnCopy() 89 | { 90 | if( !_data.ContainsKey( SETTINGS_KEY ) ) 91 | _data[ SETTINGS_KEY ] = new PBXDictionary(); 92 | 93 | var settings = _data[ SETTINGS_KEY ] as PBXDictionary; 94 | if( !settings.ContainsKey( ATTRIBUTES_KEY ) ) { 95 | var attributes = new PBXList(); 96 | attributes.Add( "CodeSignOnCopy" ); 97 | attributes.Add( "RemoveHeadersOnCopy" ); 98 | settings.Add( ATTRIBUTES_KEY, attributes ); 99 | } 100 | else { 101 | var attributes = settings[ ATTRIBUTES_KEY ] as PBXList; 102 | attributes.Add( "CodeSignOnCopy" ); 103 | attributes.Add( "RemoveHeadersOnCopy" ); 104 | } 105 | return true; 106 | } 107 | 108 | 109 | public bool AddCompilerFlag( string flag ) 110 | { 111 | if( !_data.ContainsKey( SETTINGS_KEY ) ) 112 | _data[ SETTINGS_KEY ] = new PBXDictionary(); 113 | 114 | if( !((PBXDictionary)_data[ SETTINGS_KEY ]).ContainsKey( COMPILER_FLAGS_KEY ) ) { 115 | ((PBXDictionary)_data[ SETTINGS_KEY ]).Add( COMPILER_FLAGS_KEY, flag ); 116 | return true; 117 | } 118 | 119 | string[] flags = ((string)((PBXDictionary)_data[ SETTINGS_KEY ])[ COMPILER_FLAGS_KEY ]).Split( ' ' ); 120 | foreach( string item in flags ) { 121 | if( item.CompareTo( flag ) == 0 ) 122 | return false; 123 | } 124 | 125 | ((PBXDictionary)_data[ SETTINGS_KEY ])[ COMPILER_FLAGS_KEY ] = ( string.Join( " ", flags ) + " " + flag ); 126 | return true; 127 | } 128 | 129 | } 130 | } 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXObject.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR && UNITY_IPHONE 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace UnityEditor.XCodeEditor 9 | { 10 | public class PBXObject 11 | { 12 | protected const string ISA_KEY = "isa"; 13 | 14 | protected string _guid; 15 | protected PBXDictionary _data; 16 | 17 | #region Properties 18 | 19 | public string guid { 20 | get { 21 | if( string.IsNullOrEmpty( _guid ) ) 22 | _guid = GenerateGuid(); 23 | 24 | return _guid; 25 | } 26 | } 27 | 28 | public PBXDictionary data { 29 | get { 30 | if( _data == null ) 31 | _data = new PBXDictionary(); 32 | 33 | return _data; 34 | } 35 | } 36 | 37 | 38 | #endregion 39 | #region Constructors 40 | 41 | public PBXObject() 42 | { 43 | _data = new PBXDictionary(); 44 | _data[ ISA_KEY ] = this.GetType().Name; 45 | _guid = GenerateGuid(); 46 | } 47 | 48 | public PBXObject( string guid ) : this() 49 | { 50 | if( IsGuid( guid ) ) 51 | _guid = guid; 52 | } 53 | 54 | public PBXObject( string guid, PBXDictionary dictionary ) : this( guid ) 55 | { 56 | if( !dictionary.ContainsKey( ISA_KEY ) || ((string)dictionary[ ISA_KEY ]).CompareTo( this.GetType().Name ) != 0 ) 57 | Debug.LogError( "PBXDictionary is not a valid ISA object" ); 58 | 59 | foreach( KeyValuePair item in dictionary ) { 60 | _data[ item.Key ] = item.Value; 61 | } 62 | } 63 | 64 | #endregion 65 | #region Static methods 66 | 67 | public static bool IsGuid( string aString ) 68 | { 69 | // Note: Unity3d generates mixed-case GUIDs, Xcode use uppercase GUIDs only. 70 | return System.Text.RegularExpressions.Regex.IsMatch( aString, @"^[A-Fa-f0-9]{24}$" ); 71 | } 72 | 73 | public static string GenerateGuid() 74 | { 75 | return System.Guid.NewGuid().ToString("N").Substring( 8 ).ToUpper(); 76 | } 77 | 78 | 79 | #endregion 80 | #region Data manipulation 81 | 82 | public void Add( string key, object obj ) 83 | { 84 | _data.Add( key, obj ); 85 | } 86 | 87 | public bool Remove( string key ) 88 | { 89 | return _data.Remove( key ); 90 | } 91 | 92 | public bool ContainsKey( string key ) 93 | { 94 | return _data.ContainsKey( key ); 95 | } 96 | 97 | #endregion 98 | #region syntactic sugar 99 | /// 100 | /// This allows us to use the form: 101 | /// "if (x)" or "if (!x)" 102 | /// 103 | public static implicit operator bool( PBXObject x ) { 104 | //if null or no data, treat us as false/null 105 | return (x == null) ? false : (x.data.Count == 0); 106 | } 107 | 108 | /// 109 | /// I find this handy. return our fields as comma-separated values 110 | /// 111 | public string ToCSV() { 112 | return "\"" + data + "\", "; 113 | } 114 | 115 | /// 116 | /// Concatenate and format so appears as "{,,,}" 117 | /// 118 | public override string ToString() { 119 | return "{" + this.ToCSV() + "} "; 120 | } 121 | #endregion 122 | } 123 | 124 | public class PBXNativeTarget : PBXObject 125 | { 126 | public PBXNativeTarget() : base() { 127 | } 128 | 129 | public PBXNativeTarget( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 130 | } 131 | } 132 | 133 | public class PBXContainerItemProxy : PBXObject 134 | { 135 | public PBXContainerItemProxy() : base() { 136 | } 137 | 138 | public PBXContainerItemProxy( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 139 | } 140 | } 141 | 142 | public class PBXReferenceProxy : PBXObject 143 | { 144 | public PBXReferenceProxy() : base() { 145 | } 146 | 147 | public PBXReferenceProxy( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 148 | } 149 | } 150 | } 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/XCPlist.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if UNITY_EDITOR && UNITY_IPHONE 4 | 5 | using UnityEngine; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | 10 | namespace UnityEditor.XCodeEditor 11 | { 12 | public class XCPlist 13 | { 14 | string plistPath; 15 | bool plistModified; 16 | 17 | // URLTypes constant --- plist 18 | const string BundleUrlTypes = "CFBundleURLTypes"; 19 | const string BundleTypeRole = "CFBundleTypeRole"; 20 | const string BundleUrlName = "CFBundleURLName"; 21 | const string BundleUrlSchemes = "CFBundleURLSchemes"; 22 | 23 | // URLTypes constant --- projmods 24 | const string PlistUrlType = "urltype"; 25 | const string PlistRole = "role"; 26 | const string PlistEditor = "Editor"; 27 | const string PlistName = "name"; 28 | const string PlistSchemes = "schemes"; 29 | 30 | public XCPlist(string plistPath) 31 | { 32 | this.plistPath = plistPath; 33 | } 34 | 35 | public void Process(Hashtable plist) 36 | { 37 | if( null == plist ) 38 | { 39 | return; 40 | } 41 | 42 | Dictionary dict = (Dictionary)PlistCS.Plist.readPlist(plistPath); 43 | foreach( DictionaryEntry entry in plist) 44 | { 45 | this.AddPlistItems((string)entry.Key, entry.Value, dict); 46 | } 47 | if (plistModified) 48 | { 49 | PlistCS.Plist.writeXml(dict, plistPath); 50 | } 51 | } 52 | 53 | // http://stackoverflow.com/questions/20618809/hashtable-to-dictionary 54 | public static Dictionary HashtableToDictionary (Hashtable table) 55 | { 56 | Dictionary dict = new Dictionary(); 57 | foreach(DictionaryEntry kvp in table) 58 | dict.Add((K)kvp.Key, (V)kvp.Value); 59 | return dict; 60 | } 61 | 62 | public void AddPlistItems(string key, object value, Dictionary dict) 63 | { 64 | Debug.Log ("AddPlistItems: key=" + key); 65 | 66 | if (key.CompareTo(PlistUrlType) == 0) 67 | { 68 | processUrlTypes((ArrayList)value, dict); 69 | } 70 | else 71 | { 72 | dict[key] = HashtableToDictionary((Hashtable)value); 73 | plistModified = true; 74 | } 75 | } 76 | 77 | private void processUrlTypes(ArrayList urltypes, Dictionary dict) 78 | { 79 | List bundleUrlTypes; 80 | if (dict.ContainsKey(BundleUrlTypes)) 81 | { 82 | bundleUrlTypes = (List)dict[BundleUrlTypes]; 83 | } 84 | else 85 | { 86 | bundleUrlTypes = new List(); 87 | } 88 | 89 | foreach(Hashtable table in urltypes) 90 | { 91 | string role = (string)table[PlistRole]; 92 | if (string.IsNullOrEmpty(role)) 93 | { 94 | role = PlistEditor; 95 | } 96 | string name = (string)table[PlistName]; 97 | ArrayList shcemes = (ArrayList)table[PlistSchemes]; 98 | 99 | // new schemes 100 | List urlTypeSchemes = new List(); 101 | foreach(string s in shcemes) 102 | { 103 | urlTypeSchemes.Add(s); 104 | } 105 | 106 | Dictionary urlTypeDict = this.findUrlTypeByName(bundleUrlTypes, name); 107 | if (urlTypeDict == null) 108 | { 109 | urlTypeDict = new Dictionary(); 110 | urlTypeDict[BundleTypeRole] = role; 111 | urlTypeDict[BundleUrlName] = name; 112 | urlTypeDict[BundleUrlSchemes] = urlTypeSchemes; 113 | bundleUrlTypes.Add(urlTypeDict); 114 | } 115 | else 116 | { 117 | urlTypeDict[BundleTypeRole] = role; 118 | urlTypeDict[BundleUrlSchemes] = urlTypeSchemes; 119 | } 120 | plistModified = true; 121 | } 122 | dict[BundleUrlTypes] = bundleUrlTypes; 123 | } 124 | 125 | private Dictionary findUrlTypeByName(List bundleUrlTypes, string name) 126 | { 127 | if ((bundleUrlTypes == null) || (bundleUrlTypes.Count == 0)) 128 | return null; 129 | 130 | foreach(Dictionary dict in bundleUrlTypes) 131 | { 132 | string _n = (string)dict[BundleUrlName]; 133 | if (string.Compare(_n, name) == 0) 134 | { 135 | return dict; 136 | } 137 | } 138 | return null; 139 | } 140 | } 141 | } 142 | 143 | #endif 144 | 145 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/XCMod.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR && UNITY_IPHONE 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | using System.IO; 7 | 8 | namespace UnityEditor.XCodeEditor 9 | { 10 | public class XCMod 11 | { 12 | private Hashtable _datastore = new Hashtable(); 13 | private ArrayList _libs = null; 14 | 15 | public string name { get; private set; } 16 | public string path { get; private set; } 17 | 18 | public string bundle_identifier { 19 | get { 20 | if (_datastore != null && _datastore.Contains("bundle_identifier")) 21 | return (string)_datastore["bundle_identifier"]; 22 | return string.Empty; 23 | } 24 | } 25 | 26 | public string enable_ObjectC_Exceptions { 27 | get { 28 | if (_datastore != null && _datastore.Contains("enable _ObjectC_Exceptions")) 29 | return (string)_datastore["enable _ObjectC_Exceptions"]; 30 | return string.Empty; 31 | } 32 | } 33 | public string enable_bitcode { 34 | get { 35 | if (_datastore != null && _datastore.Contains("enable_bicode")) 36 | return (string)_datastore["enable_bitcode"]; 37 | return string.Empty; 38 | } 39 | } 40 | public string group { 41 | get { 42 | if (_datastore != null && _datastore.Contains("group")) 43 | return (string)_datastore["group"]; 44 | return string.Empty; 45 | } 46 | } 47 | 48 | public ArrayList patches { 49 | get { 50 | return (ArrayList)_datastore["patches"]; 51 | } 52 | } 53 | 54 | public ArrayList libs { 55 | get { 56 | if( _libs == null ) { 57 | _libs = new ArrayList( ((ArrayList)_datastore["libs"]).Count ); 58 | foreach( string fileRef in (ArrayList)_datastore["libs"] ) { 59 | Debug.Log("Adding to Libs: "+fileRef); 60 | _libs.Add( new XCModFile( fileRef ) ); 61 | } 62 | } 63 | return _libs; 64 | } 65 | } 66 | 67 | public ArrayList frameworks { 68 | get { 69 | return (ArrayList)_datastore["frameworks"]; 70 | } 71 | } 72 | 73 | public ArrayList headerpaths { 74 | get { 75 | return (ArrayList)_datastore["headerpaths"]; 76 | } 77 | } 78 | 79 | public ArrayList files { 80 | get { 81 | return (ArrayList)_datastore["files"]; 82 | } 83 | } 84 | 85 | public ArrayList folders { 86 | get { 87 | return (ArrayList)_datastore["folders"]; 88 | } 89 | } 90 | 91 | public ArrayList excludes { 92 | get { 93 | return (ArrayList)_datastore["excludes"]; 94 | } 95 | } 96 | 97 | public ArrayList compiler_flags { 98 | get { 99 | return (ArrayList)_datastore["compiler_flags"]; 100 | } 101 | } 102 | 103 | public ArrayList linker_flags { 104 | get { 105 | return (ArrayList)_datastore["linker_flags"]; 106 | } 107 | } 108 | 109 | public ArrayList embed_binaries { 110 | get { 111 | return (ArrayList)_datastore["embed_binaries"]; 112 | } 113 | } 114 | 115 | public Hashtable plist { 116 | get { 117 | return (Hashtable)_datastore["plist"]; 118 | } 119 | } 120 | 121 | public XCMod( string filename ) 122 | { 123 | FileInfo projectFileInfo = new FileInfo( filename ); 124 | if( !projectFileInfo.Exists ) { 125 | Debug.LogWarning( "File does not exist." ); 126 | } 127 | 128 | name = System.IO.Path.GetFileNameWithoutExtension( filename ); 129 | path = System.IO.Path.GetDirectoryName( filename ); 130 | if( ! path.EndsWith( "/" ) ) 131 | { 132 | path += "/"; 133 | } 134 | 135 | string contents = projectFileInfo.OpenText().ReadToEnd(); 136 | Debug.Log (contents); 137 | _datastore = (Hashtable)XUPorterJSON.MiniJSON.jsonDecode( contents ); 138 | 139 | if (_datastore == null || _datastore.Count == 0) 140 | { 141 | Debug.Log (contents); 142 | throw new UnityException("Parse error in file " + System.IO.Path.GetFileName(filename) + "! Check for typos such as unbalanced quotation marks, etc."); 143 | } 144 | else 145 | { 146 | ArrayList argFile = this.files; 147 | if( null != argFile ) 148 | { 149 | for( int lCnt = 0; lCnt < argFile.Count; ++lCnt ) 150 | { 151 | argFile[lCnt] = path + (string)argFile[lCnt]; 152 | } 153 | } 154 | 155 | ArrayList argFolders = this.folders; 156 | if( null != argFolders ) 157 | { 158 | for( int lCnt = 0; lCnt < argFolders.Count; ++lCnt ) 159 | { 160 | argFolders[lCnt] = path + (string)argFolders[lCnt]; 161 | } 162 | } 163 | 164 | } 165 | 166 | } 167 | } 168 | 169 | public class XCModFile 170 | { 171 | public string filePath { get; private set; } 172 | public bool isWeak { get; private set; } 173 | 174 | public XCModFile( string inputString ) 175 | { 176 | isWeak = false; 177 | 178 | if( inputString.Contains( ":" ) ) { 179 | string[] parts = inputString.Split( ':' ); 180 | filePath = parts[0]; 181 | isWeak = ( parts[1].CompareTo( "weak" ) == 0 ); 182 | } 183 | else { 184 | filePath = inputString; 185 | } 186 | } 187 | } 188 | } 189 | 190 | #endif 191 | 192 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXFileReference.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace UnityEditor.XCodeEditor 8 | { 9 | public class PBXFileReference : PBXObject 10 | { 11 | protected const string PATH_KEY = "path"; 12 | protected const string NAME_KEY = "name"; 13 | protected const string SOURCETREE_KEY = "sourceTree"; 14 | protected const string EXPLICIT_FILE_TYPE_KEY = "explicitFileType"; 15 | protected const string LASTKNOWN_FILE_TYPE_KEY = "lastKnownFileType"; 16 | protected const string ENCODING_KEY = "fileEncoding"; 17 | 18 | public string compilerFlags; 19 | public string buildPhase; 20 | public readonly Dictionary trees = new Dictionary { 21 | { TreeEnum.ABSOLUTE, "" }, 22 | { TreeEnum.GROUP, "" }, 23 | { TreeEnum.BUILT_PRODUCTS_DIR, "BUILT_PRODUCTS_DIR" }, 24 | { TreeEnum.DEVELOPER_DIR, "DEVELOPER_DIR" }, 25 | { TreeEnum.SDKROOT, "SDKROOT" }, 26 | { TreeEnum.SOURCE_ROOT, "SOURCE_ROOT" } 27 | }; 28 | 29 | public static readonly Dictionary typeNames = new Dictionary { 30 | { ".a", "archive.ar" }, 31 | { ".app", "wrapper.application" }, 32 | { ".s", "sourcecode.asm" }, 33 | { ".c", "sourcecode.c.c" }, 34 | { ".cpp", "sourcecode.cpp.cpp" }, 35 | { ".framework", "wrapper.framework" }, 36 | { ".h", "sourcecode.c.h" }, 37 | { ".pch", "sourcecode.c.h" }, 38 | { ".icns", "image.icns" }, 39 | { ".m", "sourcecode.c.objc" }, 40 | { ".mm", "sourcecode.cpp.objcpp" }, 41 | { ".nib", "wrapper.nib" }, 42 | { ".plist", "text.plist.xml" }, 43 | { ".png", "image.png" }, 44 | { ".rtf", "text.rtf" }, 45 | { ".tiff", "image.tiff" }, 46 | { ".txt", "text" }, 47 | { ".xcodeproj", "wrapper.pb-project" }, 48 | { ".xib", "file.xib" }, 49 | { ".strings", "text.plist.strings" }, 50 | { ".bundle", "wrapper.plug-in" }, 51 | { ".dylib", "compiled.mach-o.dylib" }, 52 | { ".json", "text.json" }, 53 | {".xml","text.xml"}, 54 | {".tbd","sourcecode.text-based-dylib-definition"}, 55 | }; 56 | 57 | public static readonly Dictionary typePhases = new Dictionary { 58 | { ".a", "PBXFrameworksBuildPhase" }, 59 | { ".app", null }, 60 | { ".s", "PBXSourcesBuildPhase" }, 61 | { ".c", "PBXSourcesBuildPhase" }, 62 | { ".cpp", "PBXSourcesBuildPhase" }, 63 | { ".framework", "PBXFrameworksBuildPhase" }, 64 | { ".h", null }, 65 | { ".pch", null }, 66 | { ".icns", "PBXResourcesBuildPhase" }, 67 | { ".m", "PBXSourcesBuildPhase" }, 68 | { ".mm", "PBXSourcesBuildPhase" }, 69 | { ".nib", "PBXResourcesBuildPhase" }, 70 | { ".plist", "PBXResourcesBuildPhase" }, 71 | { ".png", "PBXResourcesBuildPhase" }, 72 | { ".rtf", "PBXResourcesBuildPhase" }, 73 | { ".tiff", "PBXResourcesBuildPhase" }, 74 | { ".txt", "PBXResourcesBuildPhase" }, 75 | { ".json", "PBXResourcesBuildPhase" }, 76 | { ".xcodeproj", null }, 77 | { ".xib", "PBXResourcesBuildPhase" }, 78 | { ".strings", "PBXResourcesBuildPhase" }, 79 | { ".bundle", "PBXResourcesBuildPhase" }, 80 | { ".dylib", "PBXFrameworksBuildPhase" }, 81 | {".xml","PBXResourcesBuildPhase"}, 82 | {".tbd","PBXFrameworksBuildPhase"}, 83 | }; 84 | 85 | public PBXFileReference( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) 86 | { 87 | 88 | } 89 | 90 | //TODO see if XCode has a preference for ordering these attributes 91 | public PBXFileReference( string filePath, TreeEnum tree = TreeEnum.SOURCE_ROOT ) : base() 92 | { 93 | this.Add( PATH_KEY, filePath ); 94 | this.Add( NAME_KEY, System.IO.Path.GetFileName( filePath ) ); 95 | this.Add( SOURCETREE_KEY, (string)( System.IO.Path.IsPathRooted( filePath ) ? trees[TreeEnum.ABSOLUTE] : trees[tree] ) ); 96 | this.GuessFileType(); 97 | } 98 | 99 | public string name { 100 | get { 101 | if( !ContainsKey( NAME_KEY ) ) { 102 | return null; 103 | } 104 | return (string)_data[NAME_KEY]; 105 | } 106 | } 107 | 108 | public string path { 109 | get { 110 | if( !ContainsKey( PATH_KEY ) ) { 111 | return null; 112 | } 113 | return (string)_data[PATH_KEY]; 114 | } 115 | } 116 | 117 | private void GuessFileType() 118 | { 119 | this.Remove( EXPLICIT_FILE_TYPE_KEY ); 120 | this.Remove( LASTKNOWN_FILE_TYPE_KEY ); 121 | string extension = System.IO.Path.GetExtension( (string)_data[ PATH_KEY ] ); 122 | if( !PBXFileReference.typeNames.ContainsKey( extension ) ){ 123 | Debug.LogWarning( "Unknown file extension: " + extension + "\nPlease add extension and Xcode type to PBXFileReference.types" ); 124 | return; 125 | } 126 | 127 | this.Add( LASTKNOWN_FILE_TYPE_KEY, PBXFileReference.typeNames[ extension ] ); 128 | this.buildPhase = PBXFileReference.typePhases[ extension ]; 129 | } 130 | 131 | private void SetFileType( string fileType ) 132 | { 133 | this.Remove( EXPLICIT_FILE_TYPE_KEY ); 134 | this.Remove( LASTKNOWN_FILE_TYPE_KEY ); 135 | 136 | this.Add( EXPLICIT_FILE_TYPE_KEY, fileType ); 137 | } 138 | } 139 | 140 | public enum TreeEnum { 141 | ABSOLUTE, 142 | GROUP, 143 | BUILT_PRODUCTS_DIR, 144 | DEVELOPER_DIR, 145 | SDKROOT, 146 | SOURCE_ROOT 147 | } 148 | } 149 | 150 | #endif 151 | 152 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/XCBuildConfiguration.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if UNITY_EDITOR && UNITY_IPHONE 4 | 5 | using UnityEngine; 6 | using System.Collections; 7 | 8 | namespace UnityEditor.XCodeEditor 9 | { 10 | public class XCBuildConfiguration : PBXObject 11 | { 12 | protected const string BUILDSETTINGS_KEY = "buildSettings"; 13 | protected const string HEADER_SEARCH_PATHS_KEY = "HEADER_SEARCH_PATHS"; 14 | protected const string LIBRARY_SEARCH_PATHS_KEY = "LIBRARY_SEARCH_PATHS"; 15 | protected const string FRAMEWORK_SEARCH_PATHS_KEY = "FRAMEWORK_SEARCH_PATHS"; 16 | protected const string OTHER_C_FLAGS_KEY = "OTHER_CFLAGS"; 17 | protected const string OTHER_LDFLAGS_KEY = "OTHER_LDFLAGS"; 18 | 19 | protected const string ENABLE_BITCODE_KEY = "ENABLE_BITCODE"; 20 | protected const string ENABLE_OBJECT_EXCEPTIONS_KEY = "GCC_ENABLE_OBJC_EXCEPTIONS"; 21 | protected const string RODUCT_BUNDLE_IDENTIFIER_KEY = "PRODUCT_BUNDLE_IDENTIFIER"; 22 | public XCBuildConfiguration( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) 23 | { 24 | 25 | } 26 | 27 | public PBXSortedDictionary buildSettings { 28 | get { 29 | if( ContainsKey( BUILDSETTINGS_KEY ) ) { 30 | if (_data[BUILDSETTINGS_KEY].GetType() == typeof(PBXDictionary)) { 31 | PBXSortedDictionary ret = new PBXSortedDictionary(); 32 | ret.Append((PBXDictionary)_data[BUILDSETTINGS_KEY]); 33 | return ret; 34 | } 35 | return (PBXSortedDictionary)_data[BUILDSETTINGS_KEY]; 36 | } 37 | return null; 38 | } 39 | } 40 | 41 | protected bool AddSearchPaths( string path, string key, bool recursive = true ) 42 | { 43 | PBXList paths = new PBXList(); 44 | paths.Add( path ); 45 | return AddSearchPaths( paths, key, recursive ); 46 | } 47 | 48 | protected bool AddSearchPaths( PBXList paths, string key, bool recursive = true, bool quoted = false ) //we want no quoting whenever we can get away with it 49 | { 50 | //Debug.Log ("AddSearchPaths " + paths + key + (recursive?" recursive":"") + " " + (quoted?" quoted":"")); 51 | bool modified = false; 52 | 53 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 54 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 55 | 56 | foreach( string path in paths ) { 57 | string currentPath = path; 58 | //Debug.Log ("path " + currentPath); 59 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey( key ) ) { 60 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add( key, new PBXList() ); 61 | } 62 | else if( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[key] is string ) { 63 | PBXList list = new PBXList(); 64 | list.Add( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[key] ); 65 | ((PBXDictionary)_data[BUILDSETTINGS_KEY])[key] = list; 66 | } 67 | 68 | //Xcode uses space as the delimiter here, so if there's a space in the filename, we *must* quote. Escaping with slash may work when you are in the Xcode UI, in some situations, but it doesn't work here. 69 | if (currentPath.Contains(@" ")) quoted = true; 70 | 71 | if (quoted) { 72 | //if it ends in "/**", it wants to be recursive, and the "/**" needs to be _outside_ the quotes 73 | if (currentPath.EndsWith("/**")) 74 | { 75 | currentPath = "\\\"" + currentPath.Replace( "/**", "\\\"/**" ); 76 | 77 | } 78 | else 79 | { 80 | currentPath = "\\\"" + currentPath + "\\\""; 81 | } 82 | } 83 | //Debug.Log ("currentPath = " + currentPath); 84 | if( !((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[key]).Contains( currentPath ) ) { 85 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[key]).Add( currentPath ); 86 | modified = true; 87 | } 88 | } 89 | 90 | return modified; 91 | } 92 | 93 | public bool AddBundleIdentifierFlags( string flag = " ") 94 | { 95 | bool modified = false; 96 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 97 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 98 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey(RODUCT_BUNDLE_IDENTIFIER_KEY) ) { 99 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add(RODUCT_BUNDLE_IDENTIFIER_KEY , flag ); 100 | modified = true; 101 | } 102 | 103 | return modified; 104 | } 105 | 106 | public bool AddEnableBitcodeFlags( string flag = "NO") 107 | { 108 | bool modified = false; 109 | Debug.Log ("AddEnableBitcodeFlags="+modified); 110 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 111 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 112 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey(ENABLE_BITCODE_KEY ) ) { 113 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add(ENABLE_BITCODE_KEY , flag ); 114 | modified = true; 115 | } 116 | 117 | return modified; 118 | } 119 | public bool AddEnableExceptionSettingFlags(string flag = "YES") 120 | { 121 | bool modified = false; 122 | 123 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 124 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 125 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey(ENABLE_OBJECT_EXCEPTIONS_KEY) ) { 126 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add(ENABLE_OBJECT_EXCEPTIONS_KEY , flag ); 127 | modified = true; 128 | } 129 | return modified; 130 | } 131 | 132 | public bool AddHeaderSearchPaths( PBXList paths, bool recursive = true ) 133 | { 134 | return this.AddSearchPaths( paths, HEADER_SEARCH_PATHS_KEY, recursive ); 135 | } 136 | 137 | public bool AddLibrarySearchPaths( PBXList paths, bool recursive = true ) 138 | { 139 | Debug.Log ("AddLibrarySearchPaths " + paths); 140 | return this.AddSearchPaths( paths, LIBRARY_SEARCH_PATHS_KEY, recursive ); 141 | } 142 | 143 | public bool AddFrameworkSearchPaths( PBXList paths, bool recursive = true ) 144 | { 145 | return this.AddSearchPaths( paths, FRAMEWORK_SEARCH_PATHS_KEY, recursive ); 146 | } 147 | 148 | public bool AddOtherCFlags( string flag ) 149 | { 150 | //Debug.Log( "INIZIO 1" ); 151 | PBXList flags = new PBXList(); 152 | flags.Add( flag ); 153 | return AddOtherCFlags( flags ); 154 | } 155 | 156 | public bool AddOtherCFlags( PBXList flags ) 157 | { 158 | //Debug.Log( "INIZIO 2" ); 159 | 160 | bool modified = false; 161 | 162 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 163 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 164 | 165 | foreach( string flag in flags ) { 166 | 167 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey( OTHER_C_FLAGS_KEY ) ) { 168 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add( OTHER_C_FLAGS_KEY, new PBXList() ); 169 | } 170 | else if ( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ OTHER_C_FLAGS_KEY ] is string ) { 171 | string tempString = (string)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_C_FLAGS_KEY]; 172 | ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ OTHER_C_FLAGS_KEY ] = new PBXList(); 173 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_C_FLAGS_KEY]).Add( tempString ); 174 | } 175 | 176 | if( !((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_C_FLAGS_KEY]).Contains( flag ) ) { 177 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_C_FLAGS_KEY]).Add( flag ); 178 | modified = true; 179 | } 180 | } 181 | 182 | return modified; 183 | } 184 | 185 | public bool AddOtherLinkerFlags( string flag ) 186 | { 187 | PBXList flags = new PBXList(); 188 | flags.Add( flag ); 189 | return AddOtherLinkerFlags( flags ); 190 | } 191 | 192 | public bool AddOtherLinkerFlags( PBXList flags ) 193 | { 194 | bool modified = false; 195 | 196 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 197 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 198 | 199 | foreach( string flag in flags ) { 200 | 201 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey( OTHER_LDFLAGS_KEY ) ) { 202 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add( OTHER_LDFLAGS_KEY, new PBXList() ); 203 | } 204 | else if ( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ OTHER_LDFLAGS_KEY ] is string ) { 205 | string tempString = (string)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_LDFLAGS_KEY]; 206 | ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ OTHER_LDFLAGS_KEY ] = new PBXList(); 207 | if( !string.IsNullOrEmpty(tempString) ) { 208 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_LDFLAGS_KEY]).Add( tempString ); 209 | } 210 | } 211 | 212 | if( !((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_LDFLAGS_KEY]).Contains( flag ) ) { 213 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_LDFLAGS_KEY]).Add( flag ); 214 | modified = true; 215 | } 216 | } 217 | 218 | return modified; 219 | } 220 | 221 | public bool overwriteBuildSetting(string settingName, string settingValue) { 222 | Debug.Log ("overwriteBuildSetting " + settingName + " " + settingValue); 223 | bool modified = false; 224 | 225 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) { 226 | Debug.Log ("creating key " + BUILDSETTINGS_KEY); 227 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 228 | } 229 | 230 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey( settingName ) ) { 231 | Debug.Log("adding key " + settingName); 232 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add( settingName, new PBXList() ); 233 | } 234 | else if ( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ settingName ] is string ) { 235 | //Debug.Log("key is string:" + settingName); 236 | //string tempString = (string)((PBXDictionary)_data[BUILDSETTINGS_KEY])[settingName]; 237 | ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ settingName ] = new PBXList(); 238 | //((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[settingName]).Add( tempString ); 239 | } 240 | 241 | if( !((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[settingName]).Contains( settingValue ) ) { 242 | Debug.Log("setting " + settingName + " to " + settingValue); 243 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[settingName]).Add( settingValue ); 244 | modified = true; 245 | } 246 | 247 | return modified; 248 | } 249 | } 250 | } 251 | 252 | #endif 253 | -------------------------------------------------------------------------------- /IOS/Editor/DMIosBuilder.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | using UnityEngine; 4 | using UnityEditor; 5 | using System; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Diagnostics; 10 | 11 | public class DMIosBuilder : EditorWindow 12 | { 13 | private static bool s_Showed = false; 14 | private static string ExportPath = DateTime.Now.ToString("yyyyMMdd"); 15 | private static string PluginsPath = ""; 16 | private static bool s_bExportType = true; 17 | 18 | [MenuItem("BuildApp/BuildXCode")] 19 | public static void ShowBuildTool () 20 | { 21 | if( DMIosBuilder.s_Showed || ! ProcessXCodeProj() ) 22 | { 23 | return; 24 | } 25 | 26 | Vector2 v2Size = new Vector2( 400.0f, 600.0f ); 27 | DMIosBuilder window = (DMIosBuilder) EditorWindow.GetWindow ( typeof (DMIosBuilder) ); 28 | 29 | window.title = "Export XCode Project Panel"; 30 | window.maxSize = v2Size; 31 | window.minSize = v2Size; 32 | 33 | DMIosBuilder.s_bExportType = true; 34 | DMIosBuilder.s_Showed = true; 35 | 36 | window.Show(); 37 | } 38 | 39 | [MenuItem("BuildApp/ProcessXCodeProject")] 40 | private static void ShowProcessTool () 41 | { 42 | if( DMIosBuilder.s_Showed || ! ProcessXCodeProj() ) 43 | { 44 | return; 45 | } 46 | 47 | Vector2 v2Size = new Vector2( 400.0f, 600.0f ); 48 | DMIosBuilder window = (DMIosBuilder) EditorWindow.GetWindow ( typeof (DMIosBuilder) ); 49 | 50 | window.title = "Process XCode Project Panel"; 51 | window.maxSize = v2Size; 52 | window.minSize = v2Size; 53 | 54 | DMIosBuilder.s_bExportType = false; 55 | DMIosBuilder.s_Showed = true; 56 | 57 | window.Show(); 58 | } 59 | 60 | private static bool ProcessXCodeProj () 61 | { 62 | string path = EditorUtility.OpenFilePanel 63 | ( 64 | "Choose XCode ProjMode File", 65 | Application.dataPath, 66 | "projmods" ); 67 | 68 | if ( string.IsNullOrEmpty( path ) ) 69 | { 70 | UnityEngine.Debug.Log( "[Error]: Can't Find XCde Proj Cfg!!!" ); 71 | XCodePostProcess.s_projModePath = null; 72 | return false; 73 | } 74 | if (!string.IsNullOrEmpty (path)) 75 | { 76 | PluginsPath = path; 77 | } 78 | XCodePostProcess.s_projModePath = path; 79 | 80 | return true; 81 | } 82 | 83 | void OnDestroy() 84 | { 85 | DMIosBuilder.s_Showed = false; 86 | } 87 | 88 | void OnGUI () 89 | { 90 | float fYLabor = 20.0f; 91 | 92 | GUI.Label( new Rect( 0.0f, 0.0f, 400.0f, 36.0f ), 93 | string.Format( "Export Direction => [{0}]", ExportPath ) ); 94 | 95 | 96 | ExportPath = GUI.TextField( new Rect( 80.0f, fYLabor, 240.0f, 44.0f ), ExportPath ); 97 | 98 | 99 | string showText = s_bExportType ? "Export" : "Process Proj"; 100 | 101 | Color old = GUI.backgroundColor; 102 | GUI.backgroundColor = Color.red; 103 | 104 | if( 0 < ExportPath.Length && GUI.Button( new Rect( 80.0f, fYLabor + 60.0f, 240.0f, 44.0f ), showText ) ) 105 | { 106 | string pathVal = Application.dataPath.Replace( '\\', '/' ); 107 | int indexFind = pathVal.LastIndexOf( '/' ); 108 | pathVal = pathVal.Substring( 0, indexFind + 1 ) + ExportPath; 109 | 110 | if( s_bExportType) 111 | { 112 | DMCore.Build.CBuildIOHelp.DeleteDirectory( pathVal ); 113 | 114 | BuildToXCode( pathVal ); 115 | } 116 | else 117 | { 118 | XCodePostProcess.OnPostProcessBuild( BuildTarget.iOS, pathVal ); 119 | 120 | XCodePostProcess.s_projModePath = null; 121 | } 122 | 123 | 124 | CopyPlistAndAppController(pathVal,PluginsPath); 125 | CopyImageAssest(pathVal,PluginsPath); 126 | UnityEngine.Debug.Log( "Build End!!!" ); 127 | UnityEngine.Debug.Log( "Build End!!!" ); 128 | 129 | Close(); 130 | DMIosBuilder.s_Showed = false; 131 | } 132 | 133 | GUI.backgroundColor = old; 134 | } 135 | 136 | 137 | private static bool BuildToXCode( string projectName ) 138 | { 139 | try 140 | { 141 | AssetDatabase.Refresh(); 142 | 143 | List< string > listEditorScenes = new List< string >(); 144 | foreach ( EditorBuildSettingsScene scene in EditorBuildSettings.scenes ) 145 | { 146 | if ( null != scene && scene.enabled ) 147 | { 148 | listEditorScenes.Add( scene.path ); 149 | } 150 | } 151 | 152 | string res = BuildPipeline.BuildPlayer( listEditorScenes.ToArray(), projectName, BuildTarget.iOS, BuildOptions.None ); 153 | 154 | if ( res.Length > 0 ) 155 | { 156 | UnityEngine.Debug.LogError( "Build Failed => " + res ); 157 | return false; 158 | } 159 | } 160 | catch ( Exception ex ) 161 | { 162 | UnityEngine.Debug.LogError(ex.StackTrace); 163 | return false; 164 | } 165 | 166 | return true; 167 | } 168 | 169 | public static void CopyEntireDir(string sourcePath, string destPath) { 170 | string[] floderArray = System.IO.Directory.GetDirectories( destPath, "*", SearchOption.TopDirectoryOnly); 171 | for( int lCnt = 0; lCnt < floderArray.Length; ++lCnt ) 172 | { 173 | System.IO.Directory.Delete( floderArray[lCnt], true ); 174 | } 175 | //Now Create all of the directories 176 | foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)) 177 | Directory.CreateDirectory(dirPath.Replace(sourcePath, destPath)); 178 | //Copy all the files & Replaces any files with the same name 179 | foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) { 180 | 181 | File.Copy(newPath, newPath.Replace(sourcePath, destPath), true); 182 | } 183 | 184 | } 185 | 186 | public static void CopyFile(string sourcePath,string targetPath) 187 | { 188 | bool isrewrite=true; // true=覆盖已存在的同名文件,false则反之 189 | System.IO.File.Copy(sourcePath, targetPath, isrewrite); 190 | } 191 | 192 | //替换指定icon和启动图 193 | public static void CopyImageAssest(string projectPath,string copyFilePath) { 194 | string directionaryPath = Path.GetDirectoryName(copyFilePath); 195 | string imagePath = projectPath + "/Unity-iPhone/Images.xcassets"; 196 | CopyEntireDir (directionaryPath + "/Images.xcassets", imagePath); 197 | } 198 | public static void CopyPlistAndAppController(string projectPath,string copyFilePath) 199 | { 200 | string directionaryPath = Path.GetDirectoryName(copyFilePath); 201 | string classPath = projectPath + "/Classes"; 202 | 203 | CopyFile(directionaryPath + "/Info.plist",projectPath + "/Info.plist"); 204 | CopyFile(directionaryPath + "/UnityAppController.h",classPath + "/UnityAppController.h"); 205 | CopyFile (directionaryPath + "/UnityAppController.mm", classPath + "/UnityAppController.mm"); 206 | CopyFile (directionaryPath + "/il2cpp-codegen.h", projectPath + "/Libraries/libil2cpp/include/codegen/il2cpp-codegen.h"); 207 | UnityEngine.Debug.Log( "CopyFile End!!!" ); 208 | } 209 | 210 | [MenuItem("BuildApp/BuildIPA")] 211 | public static void chooseProject () 212 | { 213 | string floderPath = EditorUtility.OpenFolderPanel( "Choose Floder", Application.dataPath, "" ); 214 | if (string.IsNullOrEmpty (floderPath)) 215 | { 216 | 217 | return; 218 | } 219 | 220 | UnityEngine.Debug.Log ("Select Floder =>" + floderPath ); 221 | buildIpa (floderPath); 222 | 223 | } 224 | 225 | public static void buildIpa(string ProjectPath) 226 | { 227 | if (string.IsNullOrEmpty( ProjectPath )) 228 | { 229 | UnityEngine.Debug.Log( "projectPath is null!!" ); 230 | return; 231 | } 232 | 233 | if (System.IO.File.Exists(Path.GetFullPath(ProjectPath + "/log.text"))) 234 | { 235 | File.Delete(Path.GetFullPath(ProjectPath + "/log.text")); 236 | } 237 | plBuildXcodeIpa (ProjectPath); 238 | return; 239 | string shell = Application.dataPath + "/ipa-build.sh"; 240 | string argss = shell + " " + ProjectPath; 241 | Process process = new Process (); 242 | process.StartInfo.CreateNoWindow = false; 243 | process.StartInfo.ErrorDialog = true; 244 | process.StartInfo.UseShellExecute = false; 245 | process.StartInfo.FileName = "/bin/bash"; 246 | process.StartInfo.Arguments = argss; 247 | process.StartInfo.RedirectStandardOutput = true; 248 | process.Start(); 249 | string output = process.StandardOutput.ReadToEnd(); 250 | File.AppendAllText (ProjectPath + "/log.text", output); //将得到的信息写入文件中。 251 | UnityEngine.Debug.Log( "start build ipa!!!" ); 252 | 253 | process.WaitForExit(); 254 | process.Close(); 255 | } 256 | // 257 | public static void plBuildXcodeIpa(string ProjectPath) { 258 | if (string.IsNullOrEmpty( ProjectPath )) 259 | { 260 | UnityEngine.Debug.Log( "projectPath is null!!" ); 261 | return; 262 | } 263 | // string command = "/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal"; 264 | // string shell = ProjectPath +"/xcodebuild.sh"; 265 | // string arg1 = "cd" + " " +ProjectPath+" " +"&&"+" "; 266 | // string argss = arg1 + shell; 267 | // System.Diagnostics.Process.Start(command,argss); 268 | // UnityEngine.Debug.Log(argss); 269 | 270 | // 用于做测试 shell传递参数需要用下面方法 271 | // string shell = ProjectPath +"/shell.sh"; 272 | // string arg1 = "unity"; 273 | // string arg2 = ProjectPath +"/test.log"; 274 | // string argss = shell +" "+ arg1 +" " + arg2; 275 | // System.Diagnostics.Process.Start("/bin/bash", argss); 276 | // string shell = "/shell.sh"; 277 | // string arg = ProjectPath; 278 | // string arg1 = "unity18"; 279 | // string arg2 = "/test.log"; 280 | // string argss = shell+" "+ arg1 +" " + arg2; 281 | // Process process = new Process (); 282 | // process.StartInfo.CreateNoWindow = false; 283 | // process.StartInfo.ErrorDialog = true; 284 | // process.StartInfo.UseShellExecute = false; 285 | // process.StartInfo.FileName = "/bin/bash"; 286 | // process.StartInfo.Arguments = argss; 287 | // process.StartInfo.RedirectStandardOutput = true; 288 | // process.StartInfo.RedirectStandardInput = true; 289 | // process.StartInfo.WorkingDirectory = ProjectPath; 290 | // process.Start(); 291 | // UnityEngine.Debug.Log( "process start rojectPath = !!" +ProjectPath); 292 | // string output = process.StandardOutput.ReadToEnd(); 293 | // File.AppendAllText (ProjectPath + "/log.text", output); //将得到的信息写入文件中。 294 | // process.WaitForExit(); 295 | // process.Close(); 296 | string shell = "shell.sh"; 297 | Process process = new Process (); 298 | process.StartInfo.CreateNoWindow = false; 299 | process.StartInfo.ErrorDialog = true; 300 | process.StartInfo.UseShellExecute = false; 301 | process.StartInfo.FileName = "/bin/bash"; 302 | process.StartInfo.Arguments = shell; 303 | process.StartInfo.RedirectStandardOutput = true; 304 | process.StartInfo.RedirectStandardInput = true; 305 | process.StartInfo.WorkingDirectory = ProjectPath; 306 | process.Start(); 307 | UnityEngine.Debug.Log( "process start rojectPath = !!" +ProjectPath); 308 | string output = process.StandardOutput.ReadToEnd(); 309 | File.AppendAllText (ProjectPath + "/log.text", output); //将得到的信息写入文件中。 310 | process.WaitForExit(); 311 | process.Close(); 312 | } 313 | } 314 | 315 | 316 | #endif 317 | -------------------------------------------------------------------------------- /IOS/Editor/GarbageCodeTool.cs: -------------------------------------------------------------------------------- 1 | 2 | using UnityEditor; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Diagnostics; 8 | using System.Text; 9 | using System.Security.Cryptography; 10 | //垃圾代码参数 11 | public class GarbageCodeParam { 12 | public string className; //类名 13 | public string mainFuncName; //入口函数名 14 | public string mainFuncParam1; //入口函数参数1 15 | public string mainFuncParam2; //入口函数参数2 16 | public string mainFuncParam3; //入口函数参数3 17 | } 18 | 19 | //垃圾代码生成器 20 | public class GarbageCodeTool : EditorWindow 21 | { 22 | //生成垃圾代码函数模版个数 23 | private const int funcTplCount = 1; 24 | //函数模版委托:入口函数名,入口函数参数1,入口函数参数2,入口函数参数3 ;返回生成该函数的字符串 25 | private delegate string FuncTplHandle(string methonName, string param1, string param2, string param3); 26 | //函数模版数组 27 | private static FuncTplHandle[] arrFuncTplHandle; 28 | //管理所有垃圾代码的文件名 29 | private const string garbageCodeManagerName = "CallAllCodeManager"; 30 | private static bool isGenerateXcode = false; 31 | 32 | //c#垃圾代码变量 33 | private const string namespaceName = "PLFramework"; 34 | //生成代码的命名空间 35 | private static string mstCreateCodeFilePath = //生成代码路径 36 | UnityEngine.Application.dataPath + "/GarbageCode"; 37 | private const int maxFileCount = 1000; //垃圾代码个数 38 | 39 | //Xcode垃圾代码变量 40 | private static string mstCreateXCodeFilePath = //生成XCode代码路径 41 | UnityEngine.Application.dataPath + "/GarbageXCode"; 42 | private const int maxXCodeFileCount = 50; //XCode垃圾代码个数 43 | private static string codeFilePath= //已有C#代码路径 44 | UnityEngine.Application.dataPath + "/Script"; 45 | //生成C#垃圾代码 46 | [MenuItem("Tools/生成C#垃圾代码")] 47 | static void GenerateGarbageCode() { 48 | isGenerateXcode = false; 49 | 50 | //删除旧目录代码 51 | if (Directory.Exists(mstCreateCodeFilePath)) 52 | { 53 | Directory.Delete(mstCreateCodeFilePath, true); 54 | } 55 | AssetDatabase.Refresh(); 56 | AssetDatabase.SaveAssets(); 57 | 58 | #if !UNITY_IPHONE 59 | //只有苹果才生成垃圾代码 60 | return; 61 | #endif 62 | 63 | //重新创建目录 64 | Directory.CreateDirectory(mstCreateCodeFilePath); 65 | Directory.CreateDirectory(mstCreateCodeFilePath + "/Code"); 66 | 67 | //获取本地时间作为生成md5条件之一 68 | string timeNow = DateTime.Now.ToString(); 69 | string[] arrClassName = new string[maxFileCount]; 70 | string[] arrMethonName = new string[maxFileCount]; 71 | 72 | #region 生成所有垃圾代码 73 | StringBuilder stringBuilder = null; 74 | for (int i = 0; i < maxFileCount; i++) { 75 | //获取垃圾代码参数 76 | GarbageCodeParam funcTplParam = GetFuncTplParam(timeNow,i); 77 | 78 | //生成文件名 79 | string fileName = Path.Combine(mstCreateCodeFilePath + "/Code", funcTplParam.className + ".cs"); 80 | if (string.IsNullOrEmpty(fileName)) 81 | { 82 | continue; 83 | } 84 | 85 | //构造生成文本对象 86 | stringBuilder = new StringBuilder(); 87 | 88 | //添加命名空间 89 | if (!string.IsNullOrEmpty(namespaceName)) 90 | { 91 | stringBuilder.AppendFormat(string.Format("namespace {0}\n", namespaceName)); 92 | stringBuilder.AppendLine("{"); 93 | } 94 | stringBuilder.AppendLine(""); 95 | 96 | //生成类名 97 | stringBuilder.AppendFormat("public class {0} \n", funcTplParam.className); 98 | stringBuilder.AppendLine("{"); 99 | 100 | //随机选择一种函数模版作为类函数 101 | string funcContent = GetFuncContent(funcTplParam.mainFuncName, 102 | funcTplParam.mainFuncParam1, funcTplParam.mainFuncParam2, funcTplParam.mainFuncParam3); 103 | stringBuilder.Append(funcContent); 104 | 105 | //添加最后的括号,保存文件 106 | stringBuilder.AppendLine("}"); 107 | if (!string.IsNullOrEmpty(namespaceName)) 108 | { 109 | stringBuilder.AppendLine("}"); 110 | } 111 | File.WriteAllText(fileName, stringBuilder.ToString()); 112 | 113 | arrClassName[i] = funcTplParam.className; 114 | arrMethonName[i] = funcTplParam.mainFuncName; 115 | } 116 | #endregion 生成所有垃圾代码 117 | 118 | #region 生成调用所有垃圾代码的代码 119 | string managerFilePath = Path.Combine(mstCreateCodeFilePath, garbageCodeManagerName + ".cs"); 120 | if (string.IsNullOrEmpty(managerFilePath)) 121 | { 122 | return; 123 | } 124 | stringBuilder = new StringBuilder(); 125 | if (!string.IsNullOrEmpty(namespaceName)) 126 | { 127 | stringBuilder.AppendFormat(string.Format("namespace {0}\n", namespaceName)); 128 | stringBuilder.AppendLine("{"); 129 | } 130 | stringBuilder.AppendLine("using UnityEngine;"); 131 | stringBuilder.AppendLine("using System.Collections;"); 132 | stringBuilder.AppendLine(""); 133 | stringBuilder.AppendLine("namespace PLFramework {"); 134 | stringBuilder.AppendLine(" //垃圾代码管理器"); 135 | stringBuilder.AppendFormat("public class {0}\n", garbageCodeManagerName); 136 | stringBuilder.AppendLine(" {"); 137 | stringBuilder.AppendLine(" //调用所有垃圾代码"); 138 | stringBuilder.AppendLine(" public static void CallAllGarbageCode() {"); 139 | 140 | 141 | //调用所有垃圾代码 142 | Random rd = new Random(); 143 | for (int i = 0; i < arrClassName.Length; i++) 144 | { 145 | string className = arrClassName[i]; 146 | string methonName = arrMethonName[i]; 147 | if (className == "" || methonName == "") 148 | { 149 | continue; 150 | } 151 | int randa = rd.Next(0, 1000); 152 | int randb = rd.Next(0, 1000); 153 | int randc = rd.Next(0, 1000); 154 | stringBuilder.AppendFormat(" {0} _{1} = new {2}();\n", className, className, className); 155 | if (randc > 500) 156 | { 157 | stringBuilder.AppendFormat(" _{0}.{1}({2},{3});\n", className, methonName, randa, randb); 158 | } 159 | else 160 | { 161 | stringBuilder.AppendFormat(" _{0}.{1}({2},{3},{4});\n", className, methonName, randa, randb, randc); 162 | } 163 | stringBuilder.AppendLine(""); 164 | } 165 | 166 | 167 | stringBuilder.AppendLine(" }"); 168 | stringBuilder.AppendLine(" }"); 169 | stringBuilder.AppendLine("}"); 170 | stringBuilder.AppendLine(""); 171 | if (!string.IsNullOrEmpty(namespaceName)) 172 | { 173 | stringBuilder.AppendLine("}"); 174 | } 175 | File.WriteAllText(managerFilePath, stringBuilder.ToString()); 176 | #endregion 生成调用所有垃圾代码的代码 177 | 178 | AssetDatabase.Refresh(); 179 | AssetDatabase.SaveAssets(); 180 | 181 | EditorUtility.DisplayDialog("生成C#垃圾代码","生成完毕!", "确定"); 182 | } 183 | 184 | //生成Xcode垃圾代码 185 | [MenuItem("Tools/生成Xcode垃圾代码")] 186 | static void GenerateXCodeGarbageCodes() 187 | { 188 | isGenerateXcode = true; 189 | 190 | //删除旧目录代码 191 | if (Directory.Exists(mstCreateXCodeFilePath)) 192 | { 193 | Directory.Delete(mstCreateXCodeFilePath, true); 194 | } 195 | AssetDatabase.Refresh(); 196 | AssetDatabase.SaveAssets(); 197 | 198 | #if !UNITY_IPHONE 199 | //只有苹果才生成垃圾代码 200 | return; 201 | #endif 202 | 203 | //重新创建目录 204 | Directory.CreateDirectory(mstCreateXCodeFilePath); 205 | Directory.CreateDirectory(mstCreateXCodeFilePath + "/Code"); 206 | 207 | //获取本地时间作为生成md5条件之一 208 | string timeNow = DateTime.Now.ToString(); 209 | string[] arrClassName = new string[maxXCodeFileCount]; 210 | string[] arrMethonName = new string[maxXCodeFileCount]; 211 | 212 | #region 生成所有xcode垃圾代码 213 | StringBuilder stringBuilder = null; 214 | for (int i = 0; i < maxXCodeFileCount; i++) 215 | { 216 | //获取垃圾代码参数 217 | GarbageCodeParam funcTplParam = GetFuncTplParam(timeNow, i); 218 | 219 | //生成文件名 220 | string fileName = Path.Combine(mstCreateXCodeFilePath + "/Code", funcTplParam.className + ".mm"); 221 | if (string.IsNullOrEmpty(fileName)) 222 | { 223 | continue; 224 | } 225 | 226 | //构造生成文本对象 227 | stringBuilder = new StringBuilder(); 228 | 229 | stringBuilder.AppendLine(""); 230 | 231 | //随机选择一种函数模版作为类函数 232 | string funcContent = GetFuncContent(funcTplParam.mainFuncName, 233 | funcTplParam.mainFuncParam1, funcTplParam.mainFuncParam2, funcTplParam.mainFuncParam3); 234 | stringBuilder.Append(funcContent); 235 | 236 | //保存文件 237 | File.WriteAllText(fileName, stringBuilder.ToString()); 238 | 239 | arrClassName[i] = funcTplParam.className; 240 | arrMethonName[i] = funcTplParam.mainFuncName; 241 | } 242 | #endregion 生成所有xcode垃圾代码 243 | 244 | #region 生成调用所有垃圾代码的代码 245 | string managerFilePath = Path.Combine(mstCreateXCodeFilePath, garbageCodeManagerName + ".mm"); 246 | if (string.IsNullOrEmpty(managerFilePath)) 247 | { 248 | return; 249 | } 250 | stringBuilder = new StringBuilder(); 251 | stringBuilder.AppendLine(" //调用所有代码"); 252 | stringBuilder.AppendFormat(" void {0}()\n", garbageCodeManagerName); 253 | stringBuilder.AppendLine("{"); 254 | 255 | //调用所有垃圾代码 256 | Random rd = new Random(); 257 | for (int i = 0; i < arrMethonName.Length; i++) 258 | { 259 | string methonName = arrMethonName[i]; 260 | if (methonName == "") 261 | { 262 | continue; 263 | } 264 | int randa = rd.Next(0, 1000); 265 | int randb = rd.Next(0, 1000); 266 | int randc = rd.Next(0, 1000); 267 | 268 | stringBuilder.AppendFormat(" extern int {0}(int a,int b,int c = 0);\n", methonName); 269 | if (randc > 500) 270 | { 271 | stringBuilder.AppendFormat(" {0}({1},{2});\n", methonName, randa, randb); 272 | } 273 | else 274 | { 275 | stringBuilder.AppendFormat(" {0}({1},{2},{3});\n", methonName, randa, randb, randc); 276 | } 277 | stringBuilder.AppendLine(""); 278 | } 279 | stringBuilder.AppendLine("}"); 280 | stringBuilder.AppendLine(""); 281 | File.WriteAllText(managerFilePath, stringBuilder.ToString()); 282 | #endregion 生成调用所有垃圾代码的代码 283 | 284 | AssetDatabase.Refresh(); 285 | AssetDatabase.SaveAssets(); 286 | 287 | EditorUtility.DisplayDialog("xcode垃圾代码", "生成完毕!", "确定"); 288 | } 289 | 290 | [MenuItem("Tools/C#加入混淆代码")] 291 | 292 | public static void addGarbageCodeForExistCSFile() { 293 | #if !UNITY_IPHONE 294 | //只有苹果才生成垃圾代码 295 | return; 296 | #endif 297 | 298 | if (Directory.Exists(codeFilePath)) 299 | { 300 | plSearchFiles(codeFilePath); 301 | AssetDatabase.Refresh(); 302 | AssetDatabase.SaveAssets(); 303 | EditorUtility.DisplayDialog("混淆C#垃圾代码","生成完毕!", "确定"); 304 | } 305 | } 306 | 307 | public static void plSearchFiles(string filePath) { 308 | 309 | DirectoryInfo fileDir = new DirectoryInfo(filePath); 310 | 311 | FileSystemInfo[] fsinfos = fileDir.GetFileSystemInfos(); 312 | foreach (FileSystemInfo fsinfo in fsinfos) { 313 | string fileFullName = fsinfo.FullName; 314 | if (Directory.Exists(fileFullName)) { 315 | plSearchFiles(fsinfo.FullName); 316 | } 317 | else { 318 | 319 | if ( fileFullName.EndsWith(".cs") ) 320 | { 321 | //Encoding encoding = GetType(fileFullName); 322 | StreamReader sr = new StreamReader(fileFullName, System.Text.Encoding.UTF8); 323 | String fileContent = sr.ReadToEnd().TrimStart(); 324 | sr.Close(); 325 | sr.Dispose(); 326 | if (!string.IsNullOrEmpty(fileContent)) 327 | { 328 | string timeNow = DateTime.Now.ToString(); 329 | GarbageCodeParam funcTplParam = GetFuncTplParam(timeNow+fsinfo.Name,UnityEngine.Random.Range(fsinfos.Length, fsinfos.Length*2)); 330 | StringBuilder stringBuilder = new StringBuilder(); 331 | stringBuilder.AppendLine(fileContent); 332 | stringBuilder.AppendLine(""); 333 | stringBuilder.AppendFormat("public class {0} \n", funcTplParam.className); 334 | stringBuilder.AppendLine("{"); 335 | 336 | 337 | string funcContent = GetFuncContent(funcTplParam.mainFuncName, 338 | funcTplParam.mainFuncParam1, funcTplParam.mainFuncParam2, funcTplParam.mainFuncParam3); 339 | stringBuilder.Append(funcContent); 340 | 341 | stringBuilder.AppendLine("}"); 342 | File.WriteAllText(fsinfo.FullName, stringBuilder.ToString()); 343 | //关闭流 344 | sr.Close(); 345 | //销毁流 346 | sr.Dispose(); 347 | } else { 348 | sr.Close(); 349 | sr.Dispose(); 350 | } 351 | 352 | } 353 | 354 | } 355 | } 356 | } 357 | //根据时间字符加序号产生垃圾代码参数 358 | public static GarbageCodeParam GetFuncTplParam(string timeNow,int index) { 359 | //根据当前时间+序号+当前时间戳 生成md5值 360 | string md5 = CalcMd5(timeNow + index.ToString()+(System.DateTime.Now).ToFileTime().ToString()); 361 | md5 = "_" + md5; 362 | 363 | //用md5值生成类名、入口函数名、参数名 364 | GarbageCodeParam garbageCodeParam = new GarbageCodeParam(); 365 | garbageCodeParam.className = md5; 366 | garbageCodeParam.mainFuncName = md5 + "m"; 367 | garbageCodeParam.mainFuncParam1 = md5 + "a"; 368 | garbageCodeParam.mainFuncParam2 = md5 + UnityEngine.Random.Range(0, 100); 369 | garbageCodeParam.mainFuncParam3 = md5 + "c"; 370 | return garbageCodeParam; 371 | } 372 | 373 | //获取函数体(根据已有模版随机生成) 374 | public static string GetFuncContent(string methonName, string param1, string param2, string param3) { 375 | #if !UNITY_IPHONE 376 | //只有苹果才生成垃圾代码 377 | return ""; 378 | #endif 379 | 380 | //如果没有初始化则初始化函数模版 381 | if (null == arrFuncTplHandle || arrFuncTplHandle.Length < funcTplCount) { 382 | arrFuncTplHandle = new FuncTplHandle[funcTplCount]; 383 | arrFuncTplHandle[0] = FuncTpl1; 384 | } 385 | 386 | //随机一种模版生成函数体 387 | int randNum = UnityEngine.Random.Range(0, funcTplCount); 388 | if (arrFuncTplHandle[randNum] == null) 389 | { 390 | UnityEngine.Debug.LogError("不存在范式,索引:" + randNum.ToString()); 391 | return ""; 392 | } 393 | return arrFuncTplHandle[randNum](methonName, param1, param2, param3); 394 | } 395 | 396 | /// 397 | /// 计算字符串的MD5值 398 | /// 399 | static string CalcMd5(string source) 400 | { 401 | MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); 402 | byte[] data = System.Text.Encoding.UTF8.GetBytes(source); 403 | byte[] md5Data = md5.ComputeHash(data, 0, data.Length); 404 | md5.Clear(); 405 | 406 | string destString = ""; 407 | for (int i = 0; i < md5Data.Length; i++) 408 | { 409 | destString += System.Convert.ToString(md5Data[i], 16).PadLeft(2, '0'); 410 | } 411 | destString = destString.PadLeft(32, '0'); 412 | return destString; 413 | } 414 | 415 | 416 | //函数模版1 417 | static string FuncTpl1(string methonName, string param1, string param2, string param3) 418 | { 419 | StringBuilder stringBuilder = new StringBuilder(); 420 | stringBuilder.AppendFormat(" int {0}2(int {1})\n", methonName, param1); 421 | stringBuilder.AppendLine(" {"); 422 | stringBuilder.AppendFormat(" return (int)(3.1415926535897932384626433832795028841 * {0} * {0});\n", param1); 423 | stringBuilder.AppendLine(" }"); 424 | stringBuilder.AppendLine(""); 425 | if (isGenerateXcode) 426 | { 427 | //入口函数xcode没有public 428 | stringBuilder.AppendFormat(" int {0}(int {1},int {2},int {3} = 0) \n", methonName, param1, param2, param3); 429 | } 430 | else 431 | { 432 | stringBuilder.AppendFormat(" public int {0}(int {1},int {2},int {3} = 0) \n", methonName, param1, param2, param3); 433 | } 434 | stringBuilder.AppendLine(" {"); 435 | stringBuilder.AppendFormat(" int t{0}p = {0} * {1};\n", param1, param2); 436 | stringBuilder.AppendFormat(" if ({1} != 0 && t{0}p > {1})\n", param1,param3); 437 | stringBuilder.AppendLine(" {"); 438 | stringBuilder.AppendFormat(" t{1}p = t{1}p / {0};\n", param3, param1); 439 | stringBuilder.AppendLine(" }"); 440 | stringBuilder.AppendLine(" else"); 441 | stringBuilder.AppendLine(" {"); 442 | stringBuilder.AppendFormat(" t{1}p -= {0};\n", param3, param1); 443 | stringBuilder.AppendLine(" }"); 444 | stringBuilder.AppendLine(""); 445 | stringBuilder.AppendFormat(" return {0}2(t{1}p);\n", methonName, param1); 446 | stringBuilder.AppendLine(" }"); 447 | return stringBuilder.ToString(); 448 | } 449 | 450 | } -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/PBX Editor/PBXParser.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | using UnityEngine; 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | 10 | 11 | namespace UnityEditor.XCodeEditor 12 | { 13 | public class PBXResolver 14 | { 15 | private class PBXResolverReverseIndex : Dictionary {} 16 | 17 | private PBXDictionary objects; 18 | private string rootObject; 19 | private PBXResolverReverseIndex index; 20 | 21 | public PBXResolver( PBXDictionary pbxData ) { 22 | this.objects = (PBXDictionary)pbxData[ "objects" ]; 23 | this.index = new PBXResolverReverseIndex(); 24 | this.rootObject = (string)pbxData[ "rootObject" ]; 25 | BuildReverseIndex(); 26 | } 27 | 28 | private void BuildReverseIndex() 29 | { 30 | foreach( KeyValuePair pair in this.objects ) 31 | { 32 | if( pair.Value is PBXBuildPhase ) 33 | { 34 | foreach( string guid in ((PBXBuildPhase)pair.Value).files ) 35 | { 36 | index[ guid ] = pair.Key; 37 | } 38 | } 39 | else if( pair.Value is PBXGroup ) 40 | { 41 | foreach( string guid in ((PBXGroup)pair.Value).children ) 42 | { 43 | index[ guid ] = pair.Key; 44 | } 45 | } 46 | } 47 | } 48 | 49 | public string ResolveName( string guid ) 50 | { 51 | 52 | if (!this.objects.ContainsKey(guid)) { 53 | Debug.LogWarning(this + " ResolveName could not resolve " + guid); 54 | return string.Empty; //"UNRESOLVED GUID:" + guid; 55 | } 56 | 57 | object entity = this.objects[ guid ]; 58 | 59 | if( entity is PBXBuildFile ) 60 | { 61 | return ResolveName( ((PBXBuildFile)entity).fileRef ); 62 | } 63 | else if( entity is PBXFileReference ) 64 | { 65 | PBXFileReference casted = (PBXFileReference)entity; 66 | return casted.name != null ? casted.name : casted.path; 67 | } 68 | else if( entity is PBXGroup ) 69 | { 70 | PBXGroup casted = (PBXGroup)entity; 71 | return casted.name != null ? casted.name : casted.path; 72 | } 73 | else if( entity is PBXProject || guid == this.rootObject ) 74 | { 75 | return "Project object"; 76 | } 77 | else if( entity is PBXFrameworksBuildPhase ) 78 | { 79 | return "Frameworks"; 80 | } 81 | else if( entity is PBXResourcesBuildPhase ) 82 | { 83 | return "Resources"; 84 | } 85 | else if( entity is PBXShellScriptBuildPhase ) 86 | { 87 | return "ShellScript"; 88 | } 89 | else if( entity is PBXSourcesBuildPhase ) 90 | { 91 | return "Sources"; 92 | } 93 | else if( entity is PBXCopyFilesBuildPhase ) 94 | { 95 | return "CopyFiles"; 96 | } 97 | else if( entity is XCConfigurationList ) 98 | { 99 | XCConfigurationList casted = (XCConfigurationList)entity; 100 | //Debug.LogWarning ("XCConfigurationList " + guid + " " + casted.ToString()); 101 | 102 | if( casted.data.ContainsKey( "defaultConfigurationName" ) ) { 103 | //Debug.Log ("XCConfigurationList " + (string)casted.data[ "defaultConfigurationName" ] + " " + guid); 104 | return (string)casted.data[ "defaultConfigurationName" ]; 105 | } 106 | 107 | return null; 108 | } 109 | else if( entity is PBXNativeTarget ) 110 | { 111 | PBXNativeTarget obj = (PBXNativeTarget)entity; 112 | //Debug.LogWarning ("PBXNativeTarget " + guid + " " + obj.ToString()); 113 | 114 | if( obj.data.ContainsKey( "name" ) ) { 115 | //Debug.Log ("PBXNativeTarget " + (string)obj.data[ "name" ] + " " + guid); 116 | return (string)obj.data[ "name" ]; 117 | } 118 | 119 | return null; 120 | } 121 | else if( entity is XCBuildConfiguration ) 122 | { 123 | XCBuildConfiguration obj = (XCBuildConfiguration)entity; 124 | //Debug.LogWarning ("XCBuildConfiguration UNRESOLVED GUID:" + guid + " " + (obj==null?"":obj.ToString())); 125 | 126 | if( obj.data.ContainsKey( "name" ) ) { 127 | //Debug.Log ("XCBuildConfiguration " + (string)obj.data[ "name" ] + " " + guid + " " + (obj==null?"":obj.ToString())); 128 | return (string)obj.data[ "name" ]; 129 | } 130 | 131 | } 132 | else if( entity is PBXObject ) 133 | { 134 | PBXObject obj = (PBXObject)entity; 135 | 136 | if( obj.data.ContainsKey( "name" ) ) 137 | Debug.Log ("PBXObject " + (string)obj.data[ "name" ] + " " + guid + " " + (obj==null?"":obj.ToString())); 138 | return (string)obj.data[ "name" ]; 139 | } 140 | 141 | //return "UNRESOLVED GUID:" + guid; 142 | Debug.LogWarning ("UNRESOLVED GUID:" + guid); 143 | return null; 144 | } 145 | 146 | public string ResolveBuildPhaseNameForFile( string guid ) 147 | { 148 | if( this.objects.ContainsKey( guid ) ) 149 | { 150 | object obj = this.objects[ guid ]; 151 | 152 | if( obj is PBXObject ) 153 | { 154 | PBXObject entity = (PBXObject)obj; 155 | 156 | if( this.index.ContainsKey( entity.guid ) ) 157 | { 158 | string parent_guid = this.index[ entity.guid ]; 159 | 160 | if( this.objects.ContainsKey( parent_guid ) ) 161 | { 162 | object parent = this.objects[ parent_guid ]; 163 | 164 | if( parent is PBXBuildPhase ) { 165 | string ret = ResolveName( ((PBXBuildPhase)parent).guid ); 166 | //Debug.Log ("ResolveBuildPhaseNameForFile = " + ret); 167 | return ret; 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | return null; 175 | } 176 | 177 | } 178 | 179 | public class PBXParser 180 | { 181 | public const string PBX_HEADER_TOKEN = "// !$*UTF8*$!\n"; 182 | public const char WHITESPACE_SPACE = ' '; 183 | public const char WHITESPACE_TAB = '\t'; 184 | public const char WHITESPACE_NEWLINE = '\n'; 185 | public const char WHITESPACE_CARRIAGE_RETURN = '\r'; 186 | public const char ARRAY_BEGIN_TOKEN = '('; 187 | public const char ARRAY_END_TOKEN = ')'; 188 | public const char ARRAY_ITEM_DELIMITER_TOKEN = ','; 189 | public const char DICTIONARY_BEGIN_TOKEN = '{'; 190 | public const char DICTIONARY_END_TOKEN = '}'; 191 | public const char DICTIONARY_ASSIGN_TOKEN = '='; 192 | public const char DICTIONARY_ITEM_DELIMITER_TOKEN = ';'; 193 | public const char QUOTEDSTRING_BEGIN_TOKEN = '"'; 194 | public const char QUOTEDSTRING_END_TOKEN = '"'; 195 | public const char QUOTEDSTRING_ESCAPE_TOKEN = '\\'; 196 | public const char END_OF_FILE = (char)0x1A; 197 | public const string COMMENT_BEGIN_TOKEN = "/*"; 198 | public const string COMMENT_END_TOKEN = "*/"; 199 | public const string COMMENT_LINE_TOKEN = "//"; 200 | private const int BUILDER_CAPACITY = 20000; 201 | 202 | private char[] data; 203 | private int index; 204 | private PBXResolver resolver; 205 | 206 | public PBXDictionary Decode( string data ) 207 | { 208 | if( !data.StartsWith( PBX_HEADER_TOKEN ) ) { 209 | Debug.Log( "Wrong file format." ); 210 | return null; 211 | } 212 | 213 | data = data.Substring( 13 ); 214 | this.data = data.ToCharArray(); 215 | index = 0; 216 | 217 | return (PBXDictionary)ParseValue(); 218 | } 219 | 220 | public string Encode( PBXDictionary pbxData, bool readable = false ) 221 | { 222 | this.resolver = new PBXResolver( pbxData ); 223 | StringBuilder builder = new StringBuilder( PBX_HEADER_TOKEN, BUILDER_CAPACITY ); 224 | 225 | bool success = SerializeValue( pbxData, builder, readable ); 226 | this.resolver = null; 227 | 228 | // Xcode adds newline at the end of file 229 | builder.Append( "\n" ); 230 | 231 | return ( success ? builder.ToString() : null ); 232 | } 233 | 234 | #region Pretty Print 235 | 236 | private void Indent( StringBuilder builder, int deep ) 237 | { 238 | builder.Append( "".PadLeft( deep, '\t' ) ); 239 | } 240 | 241 | private void Endline( StringBuilder builder, bool useSpace = false ) 242 | { 243 | builder.Append( useSpace ? " " : "\n" ); 244 | } 245 | 246 | private string marker = null; 247 | private void MarkSection( StringBuilder builder, string name ) 248 | { 249 | if( marker == null && name == null ) return; 250 | 251 | if( marker != null && name != marker ) 252 | { 253 | builder.Append( String.Format( "/* End {0} section */\n", marker ) ); 254 | } 255 | 256 | if( name != null && name != marker ) 257 | { 258 | builder.Append( String.Format( "\n/* Begin {0} section */\n", name ) ); 259 | } 260 | 261 | marker = name; 262 | } 263 | 264 | private bool GUIDComment( string guid, StringBuilder builder ) 265 | { 266 | string filename = this.resolver.ResolveName( guid ); 267 | string location = this.resolver.ResolveBuildPhaseNameForFile( guid ); 268 | 269 | //Debug.Log( "RESOLVE " + guid + ": " + filename + " in " + location ); 270 | 271 | if( filename != null ) { 272 | if( location != null ) { 273 | //Debug.Log( "GUIDComment " + guid + " " + String.Format( " /* {0} in {1} */", filename, location ) ); 274 | builder.Append( String.Format( " /* {0} in {1} */", filename, location ) ); 275 | } else { 276 | //Debug.Log( "GUIDComment " + guid + " " + String.Format( " /* {0} */", filename) ); 277 | builder.Append( String.Format( " /* {0} */", filename) ); 278 | } 279 | return true; 280 | } else { 281 | //string other = this.resolver.ResolveConfigurationNameForFile( guid ); 282 | Debug.Log ("GUIDComment " + guid + " [no filename]"); 283 | } 284 | 285 | return false; 286 | } 287 | 288 | #endregion 289 | 290 | #region Move 291 | 292 | private char NextToken() 293 | { 294 | SkipWhitespaces(); 295 | return StepForeward(); 296 | } 297 | 298 | private string Peek( int step = 1 ) 299 | { 300 | string sneak = string.Empty; 301 | for( int i = 1; i <= step; i++ ) { 302 | if( data.Length - 1 < index + i ) { 303 | break; 304 | } 305 | sneak += data[ index + i ]; 306 | } 307 | return sneak; 308 | } 309 | 310 | private bool SkipWhitespaces() 311 | { 312 | bool whitespace = false; 313 | while( Regex.IsMatch( StepForeward().ToString(), @"\s" ) ) 314 | whitespace = true; 315 | 316 | StepBackward(); 317 | 318 | if( SkipComments() ) { 319 | whitespace = true; 320 | SkipWhitespaces(); 321 | } 322 | 323 | return whitespace; 324 | } 325 | 326 | private bool SkipComments() 327 | { 328 | string s = string.Empty; 329 | string tag = Peek( 2 ); 330 | switch( tag ) { 331 | case COMMENT_BEGIN_TOKEN: { 332 | while( Peek( 2 ).CompareTo( COMMENT_END_TOKEN ) != 0 ) { 333 | s += StepForeward(); 334 | } 335 | s += StepForeward( 2 ); 336 | break; 337 | } 338 | case COMMENT_LINE_TOKEN: { 339 | while( !Regex.IsMatch( StepForeward().ToString(), @"\n" ) ) 340 | continue; 341 | 342 | break; 343 | } 344 | default: 345 | return false; 346 | } 347 | return true; 348 | } 349 | 350 | private char StepForeward( int step = 1 ) 351 | { 352 | index = Math.Min( data.Length, index + step ); 353 | return data[ index ]; 354 | } 355 | 356 | private char StepBackward( int step = 1 ) 357 | { 358 | index = Math.Max( 0, index - step ); 359 | return data[ index ]; 360 | } 361 | 362 | #endregion 363 | #region Parse 364 | 365 | private object ParseValue() 366 | { 367 | switch( NextToken() ) { 368 | case END_OF_FILE: 369 | Debug.Log( "End of file" ); 370 | return null; 371 | case DICTIONARY_BEGIN_TOKEN: 372 | return ParseDictionary(); 373 | case ARRAY_BEGIN_TOKEN: 374 | return ParseArray(); 375 | case QUOTEDSTRING_BEGIN_TOKEN: 376 | return ParseString(); 377 | default: 378 | StepBackward(); 379 | return ParseEntity(); 380 | } 381 | } 382 | 383 | private PBXDictionary ParseDictionary() 384 | { 385 | SkipWhitespaces(); 386 | PBXDictionary dictionary = new PBXDictionary(); 387 | string keyString = string.Empty; 388 | object valueObject = null; 389 | 390 | bool complete = false; 391 | while( !complete ) { 392 | switch( NextToken() ) { 393 | case END_OF_FILE: 394 | Debug.Log( "Error: reached end of file inside a dictionary: " + index ); 395 | complete = true; 396 | break; 397 | 398 | case DICTIONARY_ITEM_DELIMITER_TOKEN: 399 | keyString = string.Empty; 400 | valueObject = null; 401 | break; 402 | 403 | case DICTIONARY_END_TOKEN: 404 | keyString = string.Empty; 405 | valueObject = null; 406 | complete = true; 407 | break; 408 | 409 | case DICTIONARY_ASSIGN_TOKEN: 410 | valueObject = ParseValue(); 411 | if (!dictionary.ContainsKey(keyString)) { 412 | dictionary.Add( keyString, valueObject ); 413 | } 414 | break; 415 | 416 | default: 417 | StepBackward(); 418 | keyString = ParseValue() as string; 419 | break; 420 | } 421 | } 422 | return dictionary; 423 | } 424 | 425 | private PBXList ParseArray() 426 | { 427 | PBXList list = new PBXList(); 428 | bool complete = false; 429 | while( !complete ) { 430 | switch( NextToken() ) { 431 | case END_OF_FILE: 432 | Debug.Log( "Error: Reached end of file inside a list: " + list ); 433 | complete = true; 434 | break; 435 | case ARRAY_END_TOKEN: 436 | complete = true; 437 | break; 438 | case ARRAY_ITEM_DELIMITER_TOKEN: 439 | break; 440 | default: 441 | StepBackward(); 442 | list.Add( ParseValue() ); 443 | break; 444 | } 445 | } 446 | return list; 447 | } 448 | 449 | private object ParseString() 450 | { 451 | string s = string.Empty; 452 | char c = StepForeward(); 453 | while( c != QUOTEDSTRING_END_TOKEN ) { 454 | s += c; 455 | 456 | if( c == QUOTEDSTRING_ESCAPE_TOKEN ) 457 | s += StepForeward(); 458 | 459 | c = StepForeward(); 460 | } 461 | 462 | return s; 463 | } 464 | 465 | private object ParseEntity() 466 | { 467 | string word = string.Empty; 468 | 469 | while( !Regex.IsMatch( Peek(), @"[;,\s=]" ) ) { 470 | word += StepForeward(); 471 | } 472 | 473 | if( word.Length != 24 && Regex.IsMatch( word, @"^\d+$" ) ) { 474 | return Int32.Parse( word ); 475 | } 476 | 477 | return word; 478 | } 479 | 480 | #endregion 481 | #region Serialize 482 | 483 | private bool SerializeValue( object value, StringBuilder builder, bool readable = false, int indent = 0 ) 484 | { 485 | if( value == null ) { 486 | builder.Append( "null" ); 487 | } 488 | else if( value is PBXObject ) { 489 | SerializeDictionary( ((PBXObject)value).data, builder, readable, indent ); 490 | } 491 | else if( value is Dictionary ) { 492 | SerializeDictionary( (Dictionary)value, builder, readable, indent ); 493 | } 494 | else if( value.GetType().IsArray ) { 495 | SerializeArray( new ArrayList( (ICollection)value ), builder, readable, indent ); 496 | } 497 | else if( value is ArrayList ) { 498 | SerializeArray( (ArrayList)value, builder, readable, indent ); 499 | } 500 | else if( value is string ) { 501 | SerializeString( (string)value, builder, false, readable ); 502 | } 503 | else if( value is Char ) { 504 | SerializeString( Convert.ToString( (char)value ), builder, false, readable ); 505 | } 506 | else if( value is bool ) { 507 | builder.Append( Convert.ToInt32( value ).ToString() ); 508 | } 509 | else if( value.GetType().IsPrimitive ) { 510 | builder.Append( Convert.ToString( value ) ); 511 | } 512 | else { 513 | Debug.LogWarning( "Error: unknown object of type " + value.GetType().Name ); 514 | return false; 515 | } 516 | 517 | return true; 518 | } 519 | 520 | private bool SerializeDictionary( Dictionary dictionary, StringBuilder builder, bool readable = false, int indent = 0 ) 521 | { 522 | builder.Append( DICTIONARY_BEGIN_TOKEN ); 523 | if( readable ) Endline( builder ); 524 | 525 | foreach( KeyValuePair pair in dictionary ) 526 | { 527 | // output section banner if necessary 528 | if( readable && indent == 1 ) MarkSection( builder, pair.Value.GetType().Name ); 529 | 530 | // indent KEY 531 | if( readable ) Indent( builder, indent + 1 ); 532 | 533 | // KEY 534 | SerializeString( pair.Key, builder, false, readable ); 535 | 536 | // = 537 | // FIX ME: cannot resolve mode because readable = false for PBXBuildFile/Reference sections 538 | builder.Append( String.Format( " {0} ", DICTIONARY_ASSIGN_TOKEN ) ); 539 | 540 | // VALUE 541 | // do not pretty-print PBXBuildFile or PBXFileReference as Xcode does 542 | //Debug.Log ("about to serialize " + pair.Value.GetType () + " " + pair.Value); 543 | SerializeValue( pair.Value, builder, ( readable && 544 | ( pair.Value.GetType() != typeof( PBXBuildFile ) ) && 545 | ( pair.Value.GetType() != typeof( PBXFileReference ) ) 546 | ), indent + 1 ); 547 | 548 | // end statement 549 | builder.Append( DICTIONARY_ITEM_DELIMITER_TOKEN ); 550 | 551 | // FIX ME: negative readable in favor of nice output for PBXBuildFile/Reference sections 552 | Endline( builder, !readable ); 553 | } 554 | 555 | // output last section banner 556 | if( readable && indent == 1 ) MarkSection( builder, null ); 557 | 558 | // indent } 559 | if( readable ) Indent( builder, indent ); 560 | 561 | builder.Append( DICTIONARY_END_TOKEN ); 562 | 563 | return true; 564 | } 565 | 566 | private bool SerializeArray( ArrayList anArray, StringBuilder builder, bool readable = false, int indent = 0 ) 567 | { 568 | builder.Append( ARRAY_BEGIN_TOKEN ); 569 | if( readable ) Endline( builder ); 570 | 571 | for( int i = 0; i < anArray.Count; i++ ) 572 | { 573 | object value = anArray[i]; 574 | 575 | if( readable ) Indent( builder, indent + 1 ); 576 | 577 | if( !SerializeValue( value, builder, readable, indent + 1 ) ) 578 | { 579 | return false; 580 | } 581 | 582 | builder.Append( ARRAY_ITEM_DELIMITER_TOKEN ); 583 | 584 | // FIX ME: negative readable in favor of nice output for PBXBuildFile/Reference sections 585 | Endline( builder, !readable ); 586 | } 587 | 588 | if( readable ) Indent( builder, indent ); 589 | builder.Append( ARRAY_END_TOKEN ); 590 | 591 | return true; 592 | } 593 | 594 | private bool SerializeString( string aString, StringBuilder builder, bool useQuotes = false, bool readable = false ) 595 | { 596 | //Debug.Log ("SerializeString " + aString); 597 | // Is a GUID? 598 | // Note: Unity3d generates mixed-case GUIDs, Xcode use uppercase GUIDs only. 599 | if( Regex.IsMatch( aString, @"^[A-Fa-f0-9]{24}$" ) ) { 600 | builder.Append( aString ); 601 | GUIDComment( aString, builder ); 602 | return true; 603 | } 604 | 605 | // Is an empty string? 606 | if( string.IsNullOrEmpty( aString ) ) { 607 | builder.Append( QUOTEDSTRING_BEGIN_TOKEN ); 608 | builder.Append( QUOTEDSTRING_END_TOKEN ); 609 | return true; 610 | } 611 | 612 | // FIX ME: Original regexp was: @"^[A-Za-z0-9_.]+$", we use modified regexp with '/-' allowed 613 | // to workaround Unity bug when all PNGs had "Libraries/" (group name) added to their paths after append 614 | if( !Regex.IsMatch( aString, @"^[A-Za-z0-9_./-]+$" ) ) { 615 | useQuotes = true; 616 | } 617 | 618 | if( useQuotes ) 619 | builder.Append( QUOTEDSTRING_BEGIN_TOKEN ); 620 | 621 | builder.Append( aString ); 622 | 623 | if( useQuotes ) 624 | builder.Append( QUOTEDSTRING_END_TOKEN ); 625 | 626 | return true; 627 | } 628 | 629 | #endregion 630 | } 631 | } 632 | 633 | #endif 634 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/MiniJSON/MiniJSON.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR && UNITY_IPHONE 2 | 3 | namespace XUPorterJSON 4 | { 5 | 6 | using System; 7 | using System.Collections; 8 | using System.Text; 9 | using System.Collections.Generic; 10 | 11 | 12 | /* Based on the JSON parser from 13 | * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html 14 | * 15 | * I simplified it so that it doesn't throw exceptions 16 | * and can be used in Unity iPhone with maximum code stripping. 17 | */ 18 | /// 19 | /// This class encodes and decodes JSON strings. 20 | /// Spec. details, see http://www.json.org/ 21 | /// 22 | /// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable. 23 | /// All numbers are parsed to doubles. 24 | /// 25 | public class MiniJSON 26 | { 27 | private const int TOKEN_NONE = 0; 28 | private const int TOKEN_CURLY_OPEN = 1; 29 | private const int TOKEN_CURLY_CLOSE = 2; 30 | private const int TOKEN_SQUARED_OPEN = 3; 31 | private const int TOKEN_SQUARED_CLOSE = 4; 32 | private const int TOKEN_COLON = 5; 33 | private const int TOKEN_COMMA = 6; 34 | private const int TOKEN_STRING = 7; 35 | private const int TOKEN_NUMBER = 8; 36 | private const int TOKEN_TRUE = 9; 37 | private const int TOKEN_FALSE = 10; 38 | private const int TOKEN_NULL = 11; 39 | private const int BUILDER_CAPACITY = 2000; 40 | 41 | /// 42 | /// On decoding, this value holds the position at which the parse failed (-1 = no error). 43 | /// 44 | protected static int lastErrorIndex = -1; 45 | protected static string lastDecode = ""; 46 | 47 | 48 | /// 49 | /// Parses the string json into a value 50 | /// 51 | /// A JSON string. 52 | /// An ArrayList, a Hashtable, a double, a string, null, true, or false 53 | public static object jsonDecode( string json ) 54 | { 55 | // save the string for debug information 56 | MiniJSON.lastDecode = json; 57 | 58 | if( json != null ) 59 | { 60 | char[] charArray = json.ToCharArray(); 61 | int index = 0; 62 | bool success = true; 63 | object value = MiniJSON.parseValue( charArray, ref index, ref success ); 64 | 65 | if( success ) 66 | MiniJSON.lastErrorIndex = -1; 67 | else 68 | MiniJSON.lastErrorIndex = index; 69 | 70 | return value; 71 | } 72 | else 73 | { 74 | return null; 75 | } 76 | } 77 | 78 | 79 | /// 80 | /// Converts a Hashtable / ArrayList / Dictionary(string,string) object into a JSON string 81 | /// 82 | /// A Hashtable / ArrayList 83 | /// A JSON encoded string, or null if object 'json' is not serializable 84 | public static string jsonEncode( object json ) 85 | { 86 | var builder = new StringBuilder( BUILDER_CAPACITY ); 87 | var success = MiniJSON.serializeValue( json, builder ); 88 | 89 | return ( success ? builder.ToString() : null ); 90 | } 91 | 92 | 93 | /// 94 | /// On decoding, this function returns the position at which the parse failed (-1 = no error). 95 | /// 96 | /// 97 | public static bool lastDecodeSuccessful() 98 | { 99 | return ( MiniJSON.lastErrorIndex == -1 ); 100 | } 101 | 102 | 103 | /// 104 | /// On decoding, this function returns the position at which the parse failed (-1 = no error). 105 | /// 106 | /// 107 | public static int getLastErrorIndex() 108 | { 109 | return MiniJSON.lastErrorIndex; 110 | } 111 | 112 | 113 | /// 114 | /// If a decoding error occurred, this function returns a piece of the JSON string 115 | /// at which the error took place. To ease debugging. 116 | /// 117 | /// 118 | public static string getLastErrorSnippet() 119 | { 120 | if( MiniJSON.lastErrorIndex == -1 ) 121 | { 122 | return ""; 123 | } 124 | else 125 | { 126 | int startIndex = MiniJSON.lastErrorIndex - 5; 127 | int endIndex = MiniJSON.lastErrorIndex + 15; 128 | if( startIndex < 0 ) 129 | startIndex = 0; 130 | 131 | if( endIndex >= MiniJSON.lastDecode.Length ) 132 | endIndex = MiniJSON.lastDecode.Length - 1; 133 | 134 | return MiniJSON.lastDecode.Substring( startIndex, endIndex - startIndex + 1 ); 135 | } 136 | } 137 | 138 | 139 | #region Parsing 140 | 141 | protected static Hashtable parseObject( char[] json, ref int index ) 142 | { 143 | Hashtable table = new Hashtable(); 144 | int token; 145 | 146 | // { 147 | nextToken( json, ref index ); 148 | 149 | bool done = false; 150 | while( !done ) 151 | { 152 | token = lookAhead( json, index ); 153 | if( token == MiniJSON.TOKEN_NONE ) 154 | { 155 | return null; 156 | } 157 | else if( token == MiniJSON.TOKEN_COMMA ) 158 | { 159 | nextToken( json, ref index ); 160 | } 161 | else if( token == MiniJSON.TOKEN_CURLY_CLOSE ) 162 | { 163 | nextToken( json, ref index ); 164 | return table; 165 | } 166 | else 167 | { 168 | // name 169 | string name = parseString( json, ref index ); 170 | if( name == null ) 171 | { 172 | return null; 173 | } 174 | 175 | // : 176 | token = nextToken( json, ref index ); 177 | if( token != MiniJSON.TOKEN_COLON ) 178 | return null; 179 | 180 | // value 181 | bool success = true; 182 | object value = parseValue( json, ref index, ref success ); 183 | if( !success ) 184 | return null; 185 | 186 | table[name] = value; 187 | } 188 | } 189 | 190 | return table; 191 | } 192 | 193 | 194 | protected static ArrayList parseArray( char[] json, ref int index ) 195 | { 196 | ArrayList array = new ArrayList(); 197 | 198 | // [ 199 | nextToken( json, ref index ); 200 | 201 | bool done = false; 202 | while( !done ) 203 | { 204 | int token = lookAhead( json, index ); 205 | if( token == MiniJSON.TOKEN_NONE ) 206 | { 207 | return null; 208 | } 209 | else if( token == MiniJSON.TOKEN_COMMA ) 210 | { 211 | nextToken( json, ref index ); 212 | } 213 | else if( token == MiniJSON.TOKEN_SQUARED_CLOSE ) 214 | { 215 | nextToken( json, ref index ); 216 | break; 217 | } 218 | else 219 | { 220 | bool success = true; 221 | object value = parseValue( json, ref index, ref success ); 222 | if( !success ) 223 | return null; 224 | 225 | array.Add( value ); 226 | } 227 | } 228 | 229 | return array; 230 | } 231 | 232 | 233 | protected static object parseValue( char[] json, ref int index, ref bool success ) 234 | { 235 | switch( lookAhead( json, index ) ) 236 | { 237 | case MiniJSON.TOKEN_STRING: 238 | return parseString( json, ref index ); 239 | case MiniJSON.TOKEN_NUMBER: 240 | return parseNumber( json, ref index ); 241 | case MiniJSON.TOKEN_CURLY_OPEN: 242 | return parseObject( json, ref index ); 243 | case MiniJSON.TOKEN_SQUARED_OPEN: 244 | return parseArray( json, ref index ); 245 | case MiniJSON.TOKEN_TRUE: 246 | nextToken( json, ref index ); 247 | return Boolean.Parse( "TRUE" ); 248 | case MiniJSON.TOKEN_FALSE: 249 | nextToken( json, ref index ); 250 | return Boolean.Parse( "FALSE" ); 251 | case MiniJSON.TOKEN_NULL: 252 | nextToken( json, ref index ); 253 | return null; 254 | case MiniJSON.TOKEN_NONE: 255 | break; 256 | } 257 | 258 | success = false; 259 | return null; 260 | } 261 | 262 | 263 | protected static string parseString( char[] json, ref int index ) 264 | { 265 | string s = ""; 266 | char c; 267 | 268 | eatWhitespace( json, ref index ); 269 | 270 | // " 271 | c = json[index++]; 272 | 273 | bool complete = false; 274 | while( !complete ) 275 | { 276 | if( index == json.Length ) 277 | break; 278 | 279 | c = json[index++]; 280 | if( c == '"' ) 281 | { 282 | complete = true; 283 | break; 284 | } 285 | else if( c == '\\' ) 286 | { 287 | if( index == json.Length ) 288 | break; 289 | 290 | c = json[index++]; 291 | if( c == '"' ) 292 | { 293 | s += '"'; 294 | } 295 | else if( c == '\\' ) 296 | { 297 | s += '\\'; 298 | } 299 | else if( c == '/' ) 300 | { 301 | s += '/'; 302 | } 303 | else if( c == 'b' ) 304 | { 305 | s += '\b'; 306 | } 307 | else if( c == 'f' ) 308 | { 309 | s += '\f'; 310 | } 311 | else if( c == 'n' ) 312 | { 313 | s += '\n'; 314 | } 315 | else if( c == 'r' ) 316 | { 317 | s += '\r'; 318 | } 319 | else if( c == 't' ) 320 | { 321 | s += '\t'; 322 | } 323 | else if( c == 'u' ) 324 | { 325 | int remainingLength = json.Length - index; 326 | if( remainingLength >= 4 ) 327 | { 328 | char[] unicodeCharArray = new char[4]; 329 | Array.Copy( json, index, unicodeCharArray, 0, 4 ); 330 | 331 | // Drop in the HTML markup for the unicode character 332 | s += "&#x" + new string( unicodeCharArray ) + ";"; 333 | 334 | /* 335 | uint codePoint = UInt32.Parse(new string(unicodeCharArray), NumberStyles.HexNumber); 336 | // convert the integer codepoint to a unicode char and add to string 337 | s += Char.ConvertFromUtf32((int)codePoint); 338 | */ 339 | 340 | // skip 4 chars 341 | index += 4; 342 | } 343 | else 344 | { 345 | break; 346 | } 347 | 348 | } 349 | } 350 | else 351 | { 352 | s += c; 353 | } 354 | 355 | } 356 | 357 | if( !complete ) 358 | return null; 359 | 360 | return s; 361 | } 362 | 363 | 364 | protected static double parseNumber( char[] json, ref int index ) 365 | { 366 | eatWhitespace( json, ref index ); 367 | 368 | int lastIndex = getLastIndexOfNumber( json, index ); 369 | int charLength = ( lastIndex - index ) + 1; 370 | char[] numberCharArray = new char[charLength]; 371 | 372 | Array.Copy( json, index, numberCharArray, 0, charLength ); 373 | index = lastIndex + 1; 374 | return Double.Parse( new string( numberCharArray ) ); // , CultureInfo.InvariantCulture); 375 | } 376 | 377 | 378 | protected static int getLastIndexOfNumber( char[] json, int index ) 379 | { 380 | int lastIndex; 381 | for( lastIndex = index; lastIndex < json.Length; lastIndex++ ) 382 | if( "0123456789+-.eE".IndexOf( json[lastIndex] ) == -1 ) 383 | { 384 | break; 385 | } 386 | return lastIndex - 1; 387 | } 388 | 389 | 390 | protected static void eatWhitespace( char[] json, ref int index ) 391 | { 392 | for( ; index < json.Length; index++ ) 393 | if( " \t\n\r".IndexOf( json[index] ) == -1 ) 394 | { 395 | break; 396 | } 397 | } 398 | 399 | 400 | protected static int lookAhead( char[] json, int index ) 401 | { 402 | int saveIndex = index; 403 | return nextToken( json, ref saveIndex ); 404 | } 405 | 406 | 407 | protected static int nextToken( char[] json, ref int index ) 408 | { 409 | eatWhitespace( json, ref index ); 410 | 411 | if( index == json.Length ) 412 | { 413 | return MiniJSON.TOKEN_NONE; 414 | } 415 | 416 | char c = json[index]; 417 | index++; 418 | switch( c ) 419 | { 420 | case '{': 421 | return MiniJSON.TOKEN_CURLY_OPEN; 422 | case '}': 423 | return MiniJSON.TOKEN_CURLY_CLOSE; 424 | case '[': 425 | return MiniJSON.TOKEN_SQUARED_OPEN; 426 | case ']': 427 | return MiniJSON.TOKEN_SQUARED_CLOSE; 428 | case ',': 429 | return MiniJSON.TOKEN_COMMA; 430 | case '"': 431 | return MiniJSON.TOKEN_STRING; 432 | case '0': 433 | case '1': 434 | case '2': 435 | case '3': 436 | case '4': 437 | case '5': 438 | case '6': 439 | case '7': 440 | case '8': 441 | case '9': 442 | case '-': 443 | return MiniJSON.TOKEN_NUMBER; 444 | case ':': 445 | return MiniJSON.TOKEN_COLON; 446 | } 447 | index--; 448 | 449 | int remainingLength = json.Length - index; 450 | 451 | // false 452 | if( remainingLength >= 5 ) 453 | { 454 | if( json[index] == 'f' && 455 | json[index + 1] == 'a' && 456 | json[index + 2] == 'l' && 457 | json[index + 3] == 's' && 458 | json[index + 4] == 'e' ) 459 | { 460 | index += 5; 461 | return MiniJSON.TOKEN_FALSE; 462 | } 463 | } 464 | 465 | // true 466 | if( remainingLength >= 4 ) 467 | { 468 | if( json[index] == 't' && 469 | json[index + 1] == 'r' && 470 | json[index + 2] == 'u' && 471 | json[index + 3] == 'e' ) 472 | { 473 | index += 4; 474 | return MiniJSON.TOKEN_TRUE; 475 | } 476 | } 477 | 478 | // null 479 | if( remainingLength >= 4 ) 480 | { 481 | if( json[index] == 'n' && 482 | json[index + 1] == 'u' && 483 | json[index + 2] == 'l' && 484 | json[index + 3] == 'l' ) 485 | { 486 | index += 4; 487 | return MiniJSON.TOKEN_NULL; 488 | } 489 | } 490 | 491 | return MiniJSON.TOKEN_NONE; 492 | } 493 | 494 | #endregion 495 | 496 | 497 | #region Serialization 498 | 499 | protected static bool serializeObjectOrArray( object objectOrArray, StringBuilder builder ) 500 | { 501 | if( objectOrArray is Hashtable ) 502 | { 503 | return serializeObject( (Hashtable)objectOrArray, builder ); 504 | } 505 | else if( objectOrArray is ArrayList ) 506 | { 507 | return serializeArray( (ArrayList)objectOrArray, builder ); 508 | } 509 | else 510 | { 511 | return false; 512 | } 513 | } 514 | 515 | 516 | protected static bool serializeObject( Hashtable anObject, StringBuilder builder ) 517 | { 518 | builder.Append( "{" ); 519 | 520 | IDictionaryEnumerator e = anObject.GetEnumerator(); 521 | bool first = true; 522 | while( e.MoveNext() ) 523 | { 524 | string key = e.Key.ToString(); 525 | object value = e.Value; 526 | 527 | if( !first ) 528 | { 529 | builder.Append( ", " ); 530 | } 531 | 532 | serializeString( key, builder ); 533 | builder.Append( ":" ); 534 | if( !serializeValue( value, builder ) ) 535 | { 536 | return false; 537 | } 538 | 539 | first = false; 540 | } 541 | 542 | builder.Append( "}" ); 543 | return true; 544 | } 545 | 546 | 547 | protected static bool serializeDictionary( Dictionary dict, StringBuilder builder ) 548 | { 549 | builder.Append( "{" ); 550 | 551 | bool first = true; 552 | foreach( var kv in dict ) 553 | { 554 | if( !first ) 555 | builder.Append( ", " ); 556 | 557 | serializeString( kv.Key, builder ); 558 | builder.Append( ":" ); 559 | serializeString( kv.Value, builder ); 560 | 561 | first = false; 562 | } 563 | 564 | builder.Append( "}" ); 565 | return true; 566 | } 567 | 568 | 569 | protected static bool serializeArray( ArrayList anArray, StringBuilder builder ) 570 | { 571 | builder.Append( "[" ); 572 | 573 | bool first = true; 574 | for( int i = 0; i < anArray.Count; i++ ) 575 | { 576 | object value = anArray[i]; 577 | 578 | if( !first ) 579 | { 580 | builder.Append( ", " ); 581 | } 582 | 583 | if( !serializeValue( value, builder ) ) 584 | { 585 | return false; 586 | } 587 | 588 | first = false; 589 | } 590 | 591 | builder.Append( "]" ); 592 | return true; 593 | } 594 | 595 | 596 | protected static bool serializeValue( object value, StringBuilder builder ) 597 | { 598 | // Type t = value.GetType(); 599 | // Debug.Log("type: " + t.ToString() + " isArray: " + t.IsArray); 600 | 601 | if( value == null ) 602 | { 603 | builder.Append( "null" ); 604 | } 605 | else if( value.GetType().IsArray ) 606 | { 607 | serializeArray( new ArrayList( (ICollection)value ), builder ); 608 | } 609 | else if( value is string ) 610 | { 611 | serializeString( (string)value, builder ); 612 | } 613 | else if( value is Char ) 614 | { 615 | serializeString( Convert.ToString( (char)value ), builder ); 616 | } 617 | else if( value is Hashtable ) 618 | { 619 | serializeObject( (Hashtable)value, builder ); 620 | } 621 | else if( value is Dictionary ) 622 | { 623 | serializeDictionary( (Dictionary)value, builder ); 624 | } 625 | else if( value is ArrayList ) 626 | { 627 | serializeArray( (ArrayList)value, builder ); 628 | } 629 | else if( ( value is Boolean ) && ( (Boolean)value == true ) ) 630 | { 631 | builder.Append( "true" ); 632 | } 633 | else if( ( value is Boolean ) && ( (Boolean)value == false ) ) 634 | { 635 | builder.Append( "false" ); 636 | } 637 | else if( value.GetType().IsPrimitive ) 638 | { 639 | serializeNumber( Convert.ToDouble( value ), builder ); 640 | } 641 | else 642 | { 643 | return false; 644 | } 645 | 646 | return true; 647 | } 648 | 649 | 650 | protected static void serializeString( string aString, StringBuilder builder ) 651 | { 652 | builder.Append( "\"" ); 653 | 654 | char[] charArray = aString.ToCharArray(); 655 | for( int i = 0; i < charArray.Length; i++ ) 656 | { 657 | char c = charArray[i]; 658 | if( c == '"' ) 659 | { 660 | builder.Append( "\\\"" ); 661 | } 662 | else if( c == '\\' ) 663 | { 664 | builder.Append( "\\\\" ); 665 | } 666 | else if( c == '\b' ) 667 | { 668 | builder.Append( "\\b" ); 669 | } 670 | else if( c == '\f' ) 671 | { 672 | builder.Append( "\\f" ); 673 | } 674 | else if( c == '\n' ) 675 | { 676 | builder.Append( "\\n" ); 677 | } 678 | else if( c == '\r' ) 679 | { 680 | builder.Append( "\\r" ); 681 | } 682 | else if( c == '\t' ) 683 | { 684 | builder.Append( "\\t" ); 685 | } 686 | else 687 | { 688 | int codepoint = Convert.ToInt32( c ); 689 | if( ( codepoint >= 32 ) && ( codepoint <= 126 ) ) 690 | { 691 | builder.Append( c ); 692 | } 693 | else 694 | { 695 | builder.Append( "\\u" + Convert.ToString( codepoint, 16 ).PadLeft( 4, '0' ) ); 696 | } 697 | } 698 | } 699 | 700 | builder.Append( "\"" ); 701 | } 702 | 703 | 704 | protected static void serializeNumber( double number, StringBuilder builder ) 705 | { 706 | builder.Append( Convert.ToString( number ) ); // , CultureInfo.InvariantCulture)); 707 | } 708 | 709 | #endregion 710 | 711 | } 712 | 713 | 714 | 715 | #region Extension methods 716 | 717 | public static class MiniJsonExtensions 718 | { 719 | public static string toJson( this Hashtable obj ) 720 | { 721 | return MiniJSON.jsonEncode( obj ); 722 | } 723 | 724 | 725 | public static string toJson( this Dictionary obj ) 726 | { 727 | return MiniJSON.jsonEncode( obj ); 728 | } 729 | 730 | 731 | public static ArrayList arrayListFromJson( this string json ) 732 | { 733 | return MiniJSON.jsonDecode( json ) as ArrayList; 734 | } 735 | 736 | 737 | public static Hashtable hashtableFromJson( this string json ) 738 | { 739 | return MiniJSON.jsonDecode( json ) as Hashtable; 740 | } 741 | } 742 | 743 | #endregion 744 | } 745 | 746 | #endif 747 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/XCProject.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if UNITY_EDITOR && UNITY_IPHONE 4 | 5 | using UnityEngine; 6 | using UnityEditor; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Text.RegularExpressions; 11 | 12 | namespace UnityEditor.XCodeEditor 13 | { 14 | public partial class XCProject : System.IDisposable 15 | { 16 | private PBXDictionary _datastore; 17 | public PBXDictionary _objects; 18 | //private PBXDictionary _configurations; 19 | 20 | private PBXGroup _rootGroup; 21 | //private string _defaultConfigurationName; 22 | private string _rootObjectKey; 23 | 24 | public string projectRootPath { get; private set; } 25 | private FileInfo projectFileInfo; 26 | 27 | public string filePath { get; private set; } 28 | //private string sourcePathRoot; 29 | private bool modified = false; 30 | 31 | #region Data 32 | 33 | // Objects 34 | private PBXSortedDictionary _buildFiles; 35 | private PBXSortedDictionary _groups; 36 | private PBXSortedDictionary _fileReferences; 37 | private PBXDictionary _nativeTargets; 38 | 39 | private PBXDictionary _frameworkBuildPhases; 40 | private PBXDictionary _resourcesBuildPhases; 41 | private PBXDictionary _shellScriptBuildPhases; 42 | private PBXDictionary _sourcesBuildPhases; 43 | private PBXDictionary _copyBuildPhases; 44 | 45 | private PBXDictionary _variantGroups; 46 | private PBXDictionary _buildConfigurations; 47 | private PBXSortedDictionary _configurationLists; 48 | 49 | private PBXProject _project; 50 | 51 | #endregion 52 | 53 | 54 | #region Constructor 55 | 56 | public XCProject() 57 | { 58 | 59 | } 60 | 61 | public XCProject( string filePath ) : this() 62 | { 63 | if( !System.IO.Directory.Exists( filePath ) ) { 64 | Debug.LogWarning( "XCode project path does not exist: " + filePath ); 65 | return; 66 | } 67 | 68 | if( filePath.EndsWith( ".xcodeproj" ) ) { 69 | Debug.Log( "Opening project " + filePath ); 70 | this.projectRootPath = Path.GetDirectoryName( filePath ); 71 | this.filePath = filePath; 72 | } else { 73 | Debug.Log( "Looking for xcodeproj files in " + filePath ); 74 | string[] projects = System.IO.Directory.GetDirectories( filePath, "*.xcodeproj" ); 75 | if( projects.Length == 0 ) { 76 | Debug.LogWarning( "Error: missing xcodeproj file" ); 77 | return; 78 | } 79 | 80 | this.projectRootPath = filePath; 81 | //if the path is relative to the project, we need to make it absolute 82 | if (!System.IO.Path.IsPathRooted(projectRootPath)) 83 | projectRootPath = Application.dataPath.Replace("Assets", "") + projectRootPath; 84 | //Debug.Log ("projectRootPath adjusted to " + projectRootPath); 85 | this.filePath = projects[ 0 ]; 86 | } 87 | 88 | projectFileInfo = new FileInfo( Path.Combine( this.filePath, "project.pbxproj" ) ); 89 | string contents = projectFileInfo.OpenText().ReadToEnd(); 90 | 91 | PBXParser parser = new PBXParser(); 92 | _datastore = parser.Decode( contents ); 93 | if( _datastore == null ) { 94 | throw new System.Exception( "Project file not found at file path " + filePath ); 95 | } 96 | 97 | if( !_datastore.ContainsKey( "objects" ) ) { 98 | Debug.Log( "Errore " + _datastore.Count ); 99 | return; 100 | } 101 | 102 | _objects = (PBXDictionary)_datastore["objects"]; 103 | modified = false; 104 | 105 | _rootObjectKey = (string)_datastore["rootObject"]; 106 | if( !string.IsNullOrEmpty( _rootObjectKey ) ) { 107 | _project = new PBXProject( _rootObjectKey, (PBXDictionary)_objects[ _rootObjectKey ] ); 108 | _rootGroup = new PBXGroup( _rootObjectKey, (PBXDictionary)_objects[ _project.mainGroupID ] ); 109 | } 110 | else { 111 | Debug.LogWarning( "error: project has no root object" ); 112 | _project = null; 113 | _rootGroup = null; 114 | } 115 | 116 | } 117 | 118 | #endregion 119 | 120 | 121 | #region Properties 122 | 123 | public PBXProject project { 124 | get { 125 | return _project; 126 | } 127 | } 128 | 129 | public PBXGroup rootGroup { 130 | get { 131 | return _rootGroup; 132 | } 133 | } 134 | 135 | public PBXSortedDictionary buildFiles { 136 | get { 137 | if( _buildFiles == null ) { 138 | _buildFiles = new PBXSortedDictionary( _objects ); 139 | } 140 | return _buildFiles; 141 | } 142 | } 143 | 144 | public PBXSortedDictionary groups { 145 | get { 146 | if( _groups == null ) { 147 | _groups = new PBXSortedDictionary( _objects ); 148 | } 149 | return _groups; 150 | } 151 | } 152 | 153 | public PBXSortedDictionary fileReferences { 154 | get { 155 | if( _fileReferences == null ) { 156 | _fileReferences = new PBXSortedDictionary( _objects ); 157 | } 158 | return _fileReferences; 159 | } 160 | } 161 | 162 | public PBXDictionary variantGroups { 163 | get { 164 | if( _variantGroups == null ) { 165 | _variantGroups = new PBXDictionary( _objects ); 166 | } 167 | return _variantGroups; 168 | } 169 | } 170 | 171 | public PBXDictionary nativeTargets { 172 | get { 173 | if( _nativeTargets == null ) { 174 | _nativeTargets = new PBXDictionary( _objects ); 175 | } 176 | return _nativeTargets; 177 | } 178 | } 179 | 180 | public PBXDictionary buildConfigurations { 181 | get { 182 | if( _buildConfigurations == null ) { 183 | _buildConfigurations = new PBXDictionary( _objects ); 184 | } 185 | return _buildConfigurations; 186 | } 187 | } 188 | 189 | public PBXSortedDictionary configurationLists { 190 | get { 191 | if( _configurationLists == null ) { 192 | _configurationLists = new PBXSortedDictionary( _objects ); 193 | } 194 | return _configurationLists; 195 | } 196 | } 197 | 198 | public PBXDictionary frameworkBuildPhases { 199 | get { 200 | if( _frameworkBuildPhases == null ) { 201 | _frameworkBuildPhases = new PBXDictionary( _objects ); 202 | } 203 | return _frameworkBuildPhases; 204 | } 205 | } 206 | 207 | public PBXDictionary resourcesBuildPhases { 208 | get { 209 | if( _resourcesBuildPhases == null ) { 210 | _resourcesBuildPhases = new PBXDictionary( _objects ); 211 | } 212 | return _resourcesBuildPhases; 213 | } 214 | } 215 | 216 | public PBXDictionary shellScriptBuildPhases { 217 | get { 218 | if( _shellScriptBuildPhases == null ) { 219 | _shellScriptBuildPhases = new PBXDictionary( _objects ); 220 | } 221 | return _shellScriptBuildPhases; 222 | } 223 | } 224 | 225 | public PBXDictionary sourcesBuildPhases { 226 | get { 227 | if( _sourcesBuildPhases == null ) { 228 | _sourcesBuildPhases = new PBXDictionary( _objects ); 229 | } 230 | return _sourcesBuildPhases; 231 | } 232 | } 233 | 234 | public PBXDictionary copyBuildPhases { 235 | get { 236 | if( _copyBuildPhases == null ) { 237 | _copyBuildPhases = new PBXDictionary( _objects ); 238 | } 239 | return _copyBuildPhases; 240 | } 241 | } 242 | 243 | #endregion 244 | 245 | 246 | #region PBXMOD 247 | public bool AddBundleIdentifierFlags(string flage) 248 | { 249 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 250 | buildConfig.Value.AddBundleIdentifierFlags(flage); 251 | } 252 | modified = true; 253 | return modified; 254 | } 255 | public bool AddEnableExceptionSettingFlags(string flage) 256 | { 257 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 258 | buildConfig.Value.AddEnableExceptionSettingFlags(flage); 259 | } 260 | modified = true; 261 | return modified; 262 | } 263 | public bool AddEnableBitcodeFlags(string flage) 264 | { 265 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 266 | buildConfig.Value.AddEnableBitcodeFlags(flage); 267 | } 268 | modified = true; 269 | return modified; 270 | } 271 | public bool AddOtherCFlags( string flag ) 272 | { 273 | return AddOtherCFlags( new PBXList( flag ) ); 274 | } 275 | 276 | public bool AddOtherCFlags( PBXList flags ) 277 | { 278 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 279 | buildConfig.Value.AddOtherCFlags( flags ); 280 | } 281 | modified = true; 282 | return modified; 283 | } 284 | 285 | public bool AddOtherLinkerFlags( string flag ) 286 | { 287 | return AddOtherLinkerFlags( new PBXList( flag ) ); 288 | } 289 | 290 | public bool AddOtherLinkerFlags( PBXList flags ) 291 | { 292 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 293 | buildConfig.Value.AddOtherLinkerFlags( flags ); 294 | } 295 | modified = true; 296 | return modified; 297 | } 298 | 299 | public bool overwriteBuildSetting( string settingName, string newValue, string buildConfigName = "all") { 300 | Debug.Log("overwriteBuildSetting " + settingName + " " + newValue + " " + buildConfigName); 301 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 302 | //Debug.Log ("build config " + buildConfig); 303 | XCBuildConfiguration b = buildConfig.Value; 304 | if ( (string)b.data["name"] == buildConfigName || (string)buildConfigName == "all") { 305 | //Debug.Log ("found " + b.data["name"] + " config"); 306 | buildConfig.Value.overwriteBuildSetting(settingName, newValue); 307 | modified = true; 308 | } else { 309 | //Debug.LogWarning ("skipping " + buildConfigName + " config " + (string)b.data["name"]); 310 | } 311 | } 312 | return modified; 313 | } 314 | 315 | public bool AddHeaderSearchPaths( string path ) 316 | { 317 | return AddHeaderSearchPaths( new PBXList( path ) ); 318 | } 319 | 320 | public bool AddHeaderSearchPaths( PBXList paths ) 321 | { 322 | Debug.Log ("AddHeaderSearchPaths " + paths); 323 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 324 | buildConfig.Value.AddHeaderSearchPaths( paths ); 325 | } 326 | modified = true; 327 | return modified; 328 | } 329 | 330 | public bool AddLibrarySearchPaths( string path ) 331 | { 332 | return AddLibrarySearchPaths( new PBXList( path ) ); 333 | } 334 | 335 | public bool AddLibrarySearchPaths( PBXList paths ) 336 | { 337 | Debug.Log ("AddLibrarySearchPaths " + paths); 338 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 339 | buildConfig.Value.AddLibrarySearchPaths( paths ); 340 | } 341 | modified = true; 342 | return modified; 343 | } 344 | 345 | public bool AddFrameworkSearchPaths( string path ) 346 | { 347 | return AddFrameworkSearchPaths( new PBXList( path ) ); 348 | } 349 | 350 | public bool AddFrameworkSearchPaths( PBXList paths ) 351 | { 352 | foreach( KeyValuePair buildConfig in buildConfigurations ) { 353 | buildConfig.Value.AddFrameworkSearchPaths( paths ); 354 | } 355 | modified = true; 356 | return modified; 357 | } 358 | 359 | public object GetObject( string guid ) 360 | { 361 | return _objects[guid]; 362 | } 363 | 364 | public PBXDictionary AddFile( string filePath, PBXGroup parent = null, string tree = "SOURCE_ROOT", bool createBuildFiles = true, bool weak = false ) 365 | { 366 | //Debug.Log("AddFile " + filePath + ", " + parent + ", " + tree + ", " + (createBuildFiles? "TRUE":"FALSE") + ", " + (weak? "TRUE":"FALSE") ); 367 | 368 | PBXDictionary results = new PBXDictionary(); 369 | if (filePath == null) { 370 | Debug.LogError ("AddFile called with null filePath"); 371 | return results; 372 | } 373 | 374 | string absPath = string.Empty; 375 | 376 | if( Path.IsPathRooted( filePath ) ) { 377 | Debug.Log( "Path is Rooted" ); 378 | absPath = filePath; 379 | } 380 | else if( tree.CompareTo( "SDKROOT" ) != 0) { 381 | absPath = Path.Combine( Application.dataPath, filePath ); 382 | } 383 | 384 | if( !( File.Exists( absPath ) || Directory.Exists( absPath ) ) && tree.CompareTo( "SDKROOT" ) != 0 ) { 385 | Debug.Log( "Missing file: " + filePath ); 386 | return results; 387 | } 388 | else if( tree.CompareTo( "SOURCE_ROOT" ) == 0 ) { 389 | Debug.Log( "Source Root File" ); 390 | System.Uri fileURI = new System.Uri( absPath ); 391 | System.Uri rootURI = new System.Uri( ( projectRootPath + "/." ) ); 392 | filePath = rootURI.MakeRelativeUri( fileURI ).ToString(); 393 | } 394 | else if( tree.CompareTo("GROUP") == 0) { 395 | Debug.Log( "Group File" ); 396 | filePath = System.IO.Path.GetFileName( filePath ); 397 | } 398 | 399 | if( parent == null ) { 400 | parent = _rootGroup; 401 | } 402 | 403 | //Check if there is already a file 404 | PBXFileReference fileReference = GetFile( System.IO.Path.GetFileName( filePath ) ); 405 | if( fileReference != null ) { 406 | Debug.Log("File already exists: " + filePath); //not a warning, because this is normal for most builds! 407 | return null; 408 | } 409 | 410 | fileReference = new PBXFileReference( filePath, (TreeEnum)System.Enum.Parse( typeof(TreeEnum), tree ) ); 411 | parent.AddChild( fileReference ); 412 | fileReferences.Add( fileReference ); 413 | results.Add( fileReference.guid, fileReference ); 414 | 415 | //Create a build file for reference 416 | if( !string.IsNullOrEmpty( fileReference.buildPhase ) && createBuildFiles ) { 417 | 418 | switch( fileReference.buildPhase ) { 419 | case "PBXFrameworksBuildPhase": 420 | foreach( KeyValuePair currentObject in frameworkBuildPhases ) { 421 | BuildAddFile(fileReference,currentObject,weak); 422 | } 423 | if ( !string.IsNullOrEmpty( absPath ) && ( tree.CompareTo( "SOURCE_ROOT" ) == 0 )) { 424 | string libraryPath = Path.Combine( "$(SRCROOT)", Path.GetDirectoryName( filePath ) ); 425 | if (File.Exists(absPath)) { 426 | this.AddLibrarySearchPaths( new PBXList( libraryPath ) ); 427 | } else { 428 | this.AddFrameworkSearchPaths( new PBXList( libraryPath ) ); 429 | } 430 | 431 | } 432 | break; 433 | case "PBXResourcesBuildPhase": 434 | foreach( KeyValuePair currentObject in resourcesBuildPhases ) { 435 | Debug.Log( "Adding Resources Build File" ); 436 | BuildAddFile(fileReference,currentObject,weak); 437 | } 438 | break; 439 | case "PBXShellScriptBuildPhase": 440 | foreach( KeyValuePair currentObject in shellScriptBuildPhases ) { 441 | Debug.Log( "Adding Script Build File" ); 442 | BuildAddFile(fileReference,currentObject,weak); 443 | } 444 | break; 445 | case "PBXSourcesBuildPhase": 446 | foreach( KeyValuePair currentObject in sourcesBuildPhases ) { 447 | Debug.Log( "Adding Source Build File" ); 448 | BuildAddFile(fileReference,currentObject,weak); 449 | } 450 | break; 451 | case "PBXCopyFilesBuildPhase": 452 | foreach( KeyValuePair currentObject in copyBuildPhases ) { 453 | Debug.Log( "Adding Copy Files Build Phase" ); 454 | BuildAddFile(fileReference,currentObject,weak); 455 | } 456 | break; 457 | case null: 458 | Debug.LogWarning( "File Not Supported: " + filePath ); 459 | break; 460 | default: 461 | Debug.LogWarning( "File Not Supported." ); 462 | return null; 463 | } 464 | } 465 | return results; 466 | } 467 | 468 | public PBXNativeTarget GetNativeTarget( string name ) 469 | { 470 | PBXNativeTarget naviTarget = null; 471 | foreach( KeyValuePair currentObject in nativeTargets ) { 472 | string targetName = (string)currentObject.Value.data["name"]; 473 | if (targetName == name) { 474 | naviTarget = currentObject.Value; 475 | break; 476 | } 477 | } 478 | return naviTarget; 479 | } 480 | 481 | public int GetBuildActionMask() 482 | { 483 | int buildActionMask = 0; 484 | foreach( var currentObject in copyBuildPhases ) 485 | { 486 | buildActionMask = (int)currentObject.Value.data["buildActionMask"]; 487 | break; 488 | } 489 | return buildActionMask; 490 | } 491 | 492 | public PBXCopyFilesBuildPhase AddEmbedFrameworkBuildPhase() 493 | { 494 | PBXCopyFilesBuildPhase phase = null; 495 | 496 | PBXNativeTarget naviTarget = GetNativeTarget("Unity-iPhone"); 497 | if (naviTarget == null) 498 | { 499 | Debug.Log("Not found Correct NativeTarget."); 500 | return phase; 501 | } 502 | 503 | //check if embed framework buildPhase exist 504 | foreach( var currentObject in copyBuildPhases ) 505 | { 506 | object nameObj = null; 507 | if (currentObject.Value.data.TryGetValue("name", out nameObj)) 508 | { 509 | string name = (string)nameObj; 510 | if (name == "Embed Frameworks") 511 | return currentObject.Value; 512 | } 513 | } 514 | 515 | int buildActionMask = this.GetBuildActionMask(); 516 | phase = new PBXCopyFilesBuildPhase(buildActionMask); 517 | var buildPhases = (ArrayList)naviTarget.data["buildPhases"]; 518 | buildPhases.Add(phase.guid);//add build phase 519 | copyBuildPhases.Add(phase); 520 | return phase; 521 | } 522 | 523 | public void AddEmbedFramework( string fileName) 524 | { 525 | Debug.Log( "Add Embed Framework: " + fileName ); 526 | 527 | //Check if there is already a file 528 | PBXFileReference fileReference = GetFile( System.IO.Path.GetFileName( fileName ) ); 529 | if( fileReference == null ) { 530 | Debug.Log("Embed Framework must added already: " + fileName); 531 | return; 532 | } 533 | 534 | var embedPhase = this.AddEmbedFrameworkBuildPhase(); 535 | if (embedPhase == null) 536 | { 537 | Debug.Log("AddEmbedFrameworkBuildPhase Failed."); 538 | return; 539 | } 540 | 541 | //create a build file 542 | PBXBuildFile buildFile = new PBXBuildFile( fileReference ); 543 | buildFile.AddCodeSignOnCopy(); 544 | buildFiles.Add( buildFile ); 545 | 546 | embedPhase.AddBuildFile(buildFile); 547 | } 548 | 549 | private void BuildAddFile (PBXFileReference fileReference, KeyValuePair currentObject,bool weak) 550 | { 551 | PBXBuildFile buildFile = new PBXBuildFile( fileReference, weak ); 552 | buildFiles.Add( buildFile ); 553 | currentObject.Value.AddBuildFile( buildFile ); 554 | } 555 | private void BuildAddFile (PBXFileReference fileReference, KeyValuePair currentObject,bool weak) 556 | { 557 | PBXBuildFile buildFile = new PBXBuildFile( fileReference, weak ); 558 | buildFiles.Add( buildFile ); 559 | currentObject.Value.AddBuildFile( buildFile ); 560 | } 561 | private void BuildAddFile (PBXFileReference fileReference, KeyValuePair currentObject,bool weak) 562 | { 563 | PBXBuildFile buildFile = new PBXBuildFile( fileReference, weak ); 564 | buildFiles.Add( buildFile ); 565 | currentObject.Value.AddBuildFile( buildFile ); 566 | } 567 | private void BuildAddFile (PBXFileReference fileReference, KeyValuePair currentObject,bool weak) 568 | { 569 | PBXBuildFile buildFile = new PBXBuildFile( fileReference, weak ); 570 | buildFiles.Add( buildFile ); 571 | currentObject.Value.AddBuildFile( buildFile ); 572 | } 573 | private void BuildAddFile (PBXFileReference fileReference, KeyValuePair currentObject,bool weak) 574 | { 575 | PBXBuildFile buildFile = new PBXBuildFile( fileReference, weak ); 576 | buildFiles.Add( buildFile ); 577 | currentObject.Value.AddBuildFile( buildFile ); 578 | } 579 | 580 | public bool AddFolder( string folderPath, PBXGroup parent = null, string[] exclude = null, bool recursive = true, bool createBuildFile = true ) 581 | { 582 | Debug.Log("Folder PATH: "+folderPath); 583 | if( !Directory.Exists( folderPath ) ){ 584 | Debug.Log("Directory doesn't exist?"); 585 | return false; 586 | } 587 | 588 | if (folderPath.EndsWith(".lproj")){ 589 | Debug.Log("Ended with .lproj"); 590 | return AddLocFolder(folderPath, parent, exclude, createBuildFile); 591 | } 592 | 593 | DirectoryInfo sourceDirectoryInfo = new DirectoryInfo( folderPath ); 594 | 595 | if( exclude == null ){ 596 | Debug.Log("Exclude was null"); 597 | exclude = new string[] {}; 598 | } 599 | 600 | if( parent == null ){ 601 | Debug.Log("Parent was null"); 602 | parent = rootGroup; 603 | } 604 | 605 | // Create group 606 | PBXGroup newGroup = GetGroup( sourceDirectoryInfo.Name, null /*relative path*/, parent ); 607 | Debug.Log("New Group created"); 608 | 609 | foreach( string directory in Directory.GetDirectories( folderPath ) ) { 610 | Debug.Log( "DIR: " + directory ); 611 | if( directory.EndsWith( ".bundle" ) ) { 612 | // Treat it like a file and copy even if not recursive 613 | // TODO also for .xcdatamodeld? 614 | Debug.LogWarning( "This is a special folder: " + directory ); 615 | AddFile( directory, newGroup, "SOURCE_ROOT", createBuildFile ); 616 | continue; 617 | } 618 | 619 | if(directory.EndsWith(".xml")) { 620 | Debug.Log( "DIR: " + directory ); 621 | } 622 | 623 | if( recursive ) { 624 | Debug.Log( "recursive" ); 625 | AddFolder( directory, newGroup, exclude, recursive, createBuildFile ); 626 | } 627 | } 628 | 629 | // Adding files. 630 | string regexExclude = string.Format( @"{0}", string.Join( "|", exclude ) ); 631 | foreach( string file in Directory.GetFiles( folderPath ) ) { 632 | if(file.EndsWith(".xml")) { 633 | Debug.Log( "DIR: " + file ); 634 | } 635 | if( Regex.IsMatch( file, regexExclude ) ) { 636 | continue; 637 | } 638 | Debug.Log("Adding Files for Folder"); 639 | AddFile( file, newGroup, "SOURCE_ROOT", createBuildFile ); 640 | } 641 | 642 | modified = true; 643 | return modified; 644 | } 645 | 646 | // We support neither recursing into nor bundles contained inside loc folders 647 | public bool AddLocFolder( string folderPath, PBXGroup parent = null, string[] exclude = null, bool createBuildFile = true) 648 | { 649 | DirectoryInfo sourceDirectoryInfo = new DirectoryInfo( folderPath ); 650 | 651 | if( exclude == null ) 652 | exclude = new string[] {}; 653 | 654 | if( parent == null ) 655 | parent = rootGroup; 656 | 657 | // Create group as needed 658 | System.Uri projectFolderURI = new System.Uri( projectFileInfo.DirectoryName ); 659 | System.Uri locFolderURI = new System.Uri( folderPath ); 660 | var relativePath = projectFolderURI.MakeRelativeUri( locFolderURI ).ToString(); 661 | PBXGroup newGroup = GetGroup( sourceDirectoryInfo.Name, relativePath, parent ); 662 | 663 | // Add loc region to project 664 | string nom = sourceDirectoryInfo.Name; 665 | string region = nom.Substring(0, nom.Length - ".lproj".Length); 666 | project.AddRegion(region); 667 | 668 | // Adding files. 669 | string regexExclude = string.Format( @"{0}", string.Join( "|", exclude ) ); 670 | foreach( string file in Directory.GetFiles( folderPath ) ) { 671 | if( Regex.IsMatch( file, regexExclude ) ) { 672 | continue; 673 | } 674 | 675 | // Add a variant group for the language as well 676 | var variant = new PBXVariantGroup(System.IO.Path.GetFileName( file ), null, "GROUP"); 677 | variantGroups.Add(variant); 678 | 679 | // The group gets a reference to the variant, not to the file itself 680 | newGroup.AddChild(variant); 681 | 682 | AddFile( file, variant, "GROUP", createBuildFile ); 683 | } 684 | 685 | modified = true; 686 | return modified; 687 | } 688 | #endregion 689 | 690 | #region Getters 691 | public PBXFileReference GetFile( string name ) 692 | { 693 | if( string.IsNullOrEmpty( name ) ) { 694 | return null; 695 | } 696 | 697 | foreach( KeyValuePair current in fileReferences ) { 698 | if( !string.IsNullOrEmpty( current.Value.name ) && current.Value.name.CompareTo( name ) == 0 ) { 699 | return current.Value; 700 | } 701 | } 702 | 703 | return null; 704 | } 705 | 706 | public PBXGroup GetGroup( string name, string path = null, PBXGroup parent = null ) 707 | { 708 | if( string.IsNullOrEmpty( name ) ) 709 | return null; 710 | 711 | if( parent == null ) parent = rootGroup; 712 | 713 | foreach( KeyValuePair current in groups ) { 714 | if( string.IsNullOrEmpty( current.Value.name ) ) { 715 | if( current.Value.path.CompareTo( name ) == 0 && parent.HasChild( current.Key ) ) { 716 | return current.Value; 717 | } 718 | } else if( current.Value.name.CompareTo( name ) == 0 && parent.HasChild( current.Key ) ) { 719 | return current.Value; 720 | } 721 | } 722 | 723 | PBXGroup result = new PBXGroup( name, path ); 724 | groups.Add( result ); 725 | parent.AddChild( result ); 726 | 727 | modified = true; 728 | return result; 729 | } 730 | 731 | #endregion 732 | 733 | #region Mods 734 | public void ApplyMod( string pbxmod ) 735 | { 736 | XCMod mod = new XCMod( pbxmod ); 737 | foreach(var lib in mod.libs){ 738 | Debug.Log("Library: "+lib); 739 | } 740 | ApplyMod( mod ); 741 | } 742 | 743 | public void ApplyMod( XCMod mod ) 744 | { 745 | PBXGroup modGroup = this.GetGroup( mod.group ); 746 | 747 | foreach( XCModFile libRef in mod.libs ) { 748 | string completeLibPath = System.IO.Path.Combine( "usr/lib", libRef.filePath ); 749 | Debug.Log ("Adding library " + completeLibPath); 750 | this.AddFile( completeLibPath, modGroup, "SDKROOT", true, libRef.isWeak ); 751 | } 752 | 753 | PBXGroup frameworkGroup = this.GetGroup( "Frameworks" ); 754 | foreach( string framework in mod.frameworks ) { 755 | string[] filename = framework.Split( ':' ); 756 | bool isWeak = ( filename.Length > 1 ) ? true : false; 757 | string completePath = System.IO.Path.Combine( "System/Library/Frameworks", filename[0] ); 758 | this.AddFile( completePath, frameworkGroup, "SDKROOT", true, isWeak ); 759 | } 760 | 761 | foreach( string filePath in mod.files ) { 762 | string absoluteFilePath = filePath; 763 | this.AddFile( absoluteFilePath, modGroup ); 764 | } 765 | 766 | if (mod.embed_binaries != null) 767 | { 768 | //1. Add LD_RUNPATH_SEARCH_PATHS for embed framework 769 | this.overwriteBuildSetting("LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks", "Release"); 770 | this.overwriteBuildSetting("LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks", "Debug"); 771 | 772 | foreach( string binary in mod.embed_binaries ) { 773 | string absoluteFilePath = System.IO.Path.Combine( mod.path, binary ); 774 | this.AddEmbedFramework(absoluteFilePath); 775 | } 776 | } 777 | 778 | foreach( string folderPath in mod.folders ) { 779 | string absoluteFolderPath = folderPath; 780 | Debug.Log ("Adding folder " + absoluteFolderPath); 781 | this.AddFolder( absoluteFolderPath, modGroup, (string[])mod.excludes.ToArray( typeof(string) ) ); 782 | } 783 | 784 | foreach( string headerpath in mod.headerpaths ) { 785 | if (headerpath.Contains("$(inherited)")) { 786 | Debug.Log ("not prepending a path to " + headerpath); 787 | this.AddHeaderSearchPaths( headerpath ); 788 | } else { 789 | string absoluteHeaderPath = System.IO.Path.Combine( mod.path, headerpath ); 790 | this.AddHeaderSearchPaths( absoluteHeaderPath ); 791 | } 792 | } 793 | 794 | foreach( string flag in mod.compiler_flags ) { 795 | this.AddOtherCFlags( flag ); 796 | } 797 | 798 | foreach( string flag in mod.linker_flags ) { 799 | this.AddOtherLinkerFlags( flag ); 800 | } 801 | 802 | if(mod.enable_bitcode !=null) { 803 | this.AddEnableBitcodeFlags(mod.enable_bitcode); 804 | } 805 | 806 | if(mod.enable_ObjectC_Exceptions !=null) { 807 | this.AddEnableExceptionSettingFlags(mod.enable_ObjectC_Exceptions); 808 | } 809 | if(mod.bundle_identifier !=null) { 810 | this.AddBundleIdentifierFlags(mod.bundle_identifier); 811 | } 812 | string plistPath = this.projectRootPath + "/Info.plist"; 813 | Debug.Log ("Adding plist items..." ); 814 | 815 | XCPlist plist = new XCPlist (plistPath); 816 | plist.Process(mod.plist); 817 | 818 | this.Consolidate(); 819 | } 820 | 821 | #endregion 822 | 823 | #region Savings 824 | public void Consolidate() 825 | { 826 | PBXDictionary consolidated = new PBXDictionary(); 827 | consolidated.Append( this.buildFiles );//sort! 828 | consolidated.Append( this.copyBuildPhases ); 829 | consolidated.Append( this.fileReferences );//sort! 830 | consolidated.Append( this.frameworkBuildPhases ); 831 | consolidated.Append( this.groups );//sort! 832 | consolidated.Append( this.nativeTargets ); 833 | consolidated.Add( project.guid, project.data );//TODO this should be named PBXProject? 834 | consolidated.Append( this.resourcesBuildPhases ); 835 | consolidated.Append( this.shellScriptBuildPhases ); 836 | consolidated.Append( this.sourcesBuildPhases ); 837 | consolidated.Append( this.variantGroups ); 838 | consolidated.Append( this.buildConfigurations ); 839 | consolidated.Append( this.configurationLists ); 840 | _objects = consolidated; 841 | consolidated = null; 842 | } 843 | 844 | public void Backup() 845 | { 846 | string backupPath = Path.Combine( this.filePath, "project.backup.pbxproj" ); 847 | 848 | // Delete previous backup file 849 | if( File.Exists( backupPath ) ) 850 | File.Delete( backupPath ); 851 | 852 | // Backup original pbxproj file first 853 | File.Copy( System.IO.Path.Combine( this.filePath, "project.pbxproj" ), backupPath ); 854 | } 855 | 856 | private void DeleteExisting(string path) 857 | { 858 | // Delete old project file 859 | if( File.Exists( path )) 860 | File.Delete( path ); 861 | } 862 | 863 | private void CreateNewProject(PBXDictionary result, string path) 864 | { 865 | PBXParser parser = new PBXParser(); 866 | StreamWriter saveFile = File.CreateText( path ); 867 | saveFile.Write( parser.Encode( result, true ) ); 868 | saveFile.Close(); 869 | } 870 | 871 | /// 872 | /// Saves a project after editing. 873 | /// 874 | public void Save() 875 | { 876 | PBXDictionary result = new PBXDictionary(); 877 | result.Add( "archiveVersion", 1 ); 878 | result.Add( "classes", new PBXDictionary() ); 879 | result.Add( "objectVersion", 46 ); 880 | 881 | Consolidate(); 882 | result.Add( "objects", _objects ); 883 | 884 | result.Add( "rootObject", _rootObjectKey ); 885 | 886 | string projectPath = Path.Combine( this.filePath, "project.pbxproj" ); 887 | 888 | // Delete old project file, in case of an IOException 'Sharing violation on path Error' 889 | DeleteExisting(projectPath); 890 | 891 | // Parse result object directly into file 892 | CreateNewProject(result,projectPath); 893 | } 894 | 895 | /** 896 | * Raw project data. 897 | */ 898 | public Dictionary objects { 899 | get { 900 | return null; 901 | } 902 | } 903 | #endregion 904 | 905 | public void Dispose() 906 | { 907 | 908 | } 909 | } 910 | } 911 | 912 | #endif 913 | 914 | -------------------------------------------------------------------------------- /IOS/Editor/XUPorter/Plist/Plist.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR && UNITY_IPHONE 3 | 4 | 5 | 6 | // PlistCS Property List (plist) serialization and parsing library. 7 | // 8 | // https://github.com/animetrics/PlistCS 9 | // 10 | // Copyright (c) 2011 Animetrics Inc. (marc@animetrics.com) 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in 20 | // all copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | // THE SOFTWARE. 29 | 30 | using System; 31 | using System.Collections; 32 | using System.Collections.Generic; 33 | using System.IO; 34 | using System.Text; 35 | using System.Xml; 36 | 37 | namespace PlistCS 38 | { 39 | public static class Plist 40 | { 41 | private static List offsetTable = new List(); 42 | private static List objectTable = new List(); 43 | private static int refCount; 44 | private static int objRefSize; 45 | private static int offsetByteSize; 46 | private static long offsetTableOffset; 47 | 48 | #region Public Functions 49 | 50 | public static object readPlist(string path) 51 | { 52 | using (FileStream f = new FileStream(path, FileMode.Open, FileAccess.Read)) 53 | { 54 | return readPlist(f, plistType.Auto); 55 | } 56 | } 57 | 58 | public static object readPlistSource(string source) 59 | { 60 | return readPlist(System.Text.Encoding.UTF8.GetBytes(source)); 61 | } 62 | 63 | public static object readPlist(byte[] data) 64 | { 65 | return readPlist(new MemoryStream(data), plistType.Auto); 66 | } 67 | 68 | public static plistType getPlistType(Stream stream) 69 | { 70 | byte[] magicHeader = new byte[8]; 71 | stream.Read(magicHeader, 0, 8); 72 | 73 | if (BitConverter.ToInt64(magicHeader, 0) == 3472403351741427810) 74 | { 75 | return plistType.Binary; 76 | } 77 | else 78 | { 79 | return plistType.Xml; 80 | } 81 | } 82 | 83 | public static object readPlist(Stream stream, plistType type) 84 | { 85 | if (type == plistType.Auto) 86 | { 87 | type = getPlistType(stream); 88 | stream.Seek(0, SeekOrigin.Begin); 89 | } 90 | 91 | if (type == plistType.Binary) 92 | { 93 | using (BinaryReader reader = new BinaryReader(stream)) 94 | { 95 | byte[] data = reader.ReadBytes((int) reader.BaseStream.Length); 96 | return readBinary(data); 97 | } 98 | } 99 | else 100 | { 101 | XmlDocument xml = new XmlDocument(); 102 | xml.XmlResolver = null; 103 | xml.Load(stream); 104 | return readXml(xml); 105 | } 106 | } 107 | 108 | public static void writeXml(object value, string path) 109 | { 110 | using (StreamWriter writer = new StreamWriter(path)) 111 | { 112 | writer.Write(writeXml(value)); 113 | } 114 | } 115 | 116 | public static void writeXml(object value, Stream stream) 117 | { 118 | using (StreamWriter writer = new StreamWriter(stream)) 119 | { 120 | writer.Write(writeXml(value)); 121 | } 122 | } 123 | 124 | public static string writeXml(object value) 125 | { 126 | using (MemoryStream ms = new MemoryStream()) 127 | { 128 | XmlWriterSettings xmlWriterSettings = new XmlWriterSettings(); 129 | xmlWriterSettings.Encoding = new System.Text.UTF8Encoding(false); 130 | xmlWriterSettings.ConformanceLevel = ConformanceLevel.Document; 131 | xmlWriterSettings.Indent = true; 132 | 133 | using (XmlWriter xmlWriter = XmlWriter.Create(ms, xmlWriterSettings)) 134 | { 135 | xmlWriter.WriteStartDocument(); 136 | //xmlWriter.WriteComment("DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\""); 137 | //xmlWriter.WriteDocType("plist", "-//Apple Computer//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null); 138 | xmlWriter.WriteStartElement("plist"); 139 | xmlWriter.WriteAttributeString("version", "1.0"); 140 | compose(value, xmlWriter); 141 | xmlWriter.WriteEndElement(); 142 | xmlWriter.WriteEndDocument(); 143 | xmlWriter.Flush(); 144 | xmlWriter.Close(); 145 | return System.Text.Encoding.UTF8.GetString(ms.ToArray()); 146 | } 147 | } 148 | } 149 | 150 | public static void writeBinary(object value, string path) 151 | { 152 | using (BinaryWriter writer = new BinaryWriter(new FileStream(path, FileMode.Create))) 153 | { 154 | writer.Write(writeBinary(value)); 155 | } 156 | } 157 | 158 | public static void writeBinary(object value, Stream stream) 159 | { 160 | using (BinaryWriter writer = new BinaryWriter(stream)) 161 | { 162 | writer.Write(writeBinary(value)); 163 | } 164 | } 165 | 166 | public static byte[] writeBinary(object value) 167 | { 168 | offsetTable.Clear(); 169 | objectTable.Clear(); 170 | refCount = 0; 171 | objRefSize = 0; 172 | offsetByteSize = 0; 173 | offsetTableOffset = 0; 174 | 175 | //Do not count the root node, subtract by 1 176 | int totalRefs = countObject(value) - 1; 177 | 178 | refCount = totalRefs; 179 | 180 | objRefSize = RegulateNullBytes(BitConverter.GetBytes(refCount)).Length; 181 | 182 | composeBinary(value); 183 | 184 | writeBinaryString("bplist00", false); 185 | 186 | offsetTableOffset = (long)objectTable.Count; 187 | 188 | offsetTable.Add(objectTable.Count - 8); 189 | 190 | offsetByteSize = RegulateNullBytes(BitConverter.GetBytes(offsetTable[offsetTable.Count-1])).Length; 191 | 192 | List offsetBytes = new List(); 193 | 194 | offsetTable.Reverse(); 195 | 196 | for (int i = 0; i < offsetTable.Count; i++) 197 | { 198 | offsetTable[i] = objectTable.Count - offsetTable[i]; 199 | byte[] buffer = RegulateNullBytes(BitConverter.GetBytes(offsetTable[i]), offsetByteSize); 200 | Array.Reverse(buffer); 201 | offsetBytes.AddRange(buffer); 202 | } 203 | 204 | objectTable.AddRange(offsetBytes); 205 | 206 | objectTable.AddRange(new byte[6]); 207 | objectTable.Add(Convert.ToByte(offsetByteSize)); 208 | objectTable.Add(Convert.ToByte(objRefSize)); 209 | 210 | var a = BitConverter.GetBytes((long) totalRefs + 1); 211 | Array.Reverse(a); 212 | objectTable.AddRange(a); 213 | 214 | objectTable.AddRange(BitConverter.GetBytes((long)0)); 215 | a = BitConverter.GetBytes(offsetTableOffset); 216 | Array.Reverse(a); 217 | objectTable.AddRange(a); 218 | 219 | return objectTable.ToArray(); 220 | } 221 | 222 | #endregion 223 | 224 | #region Private Functions 225 | 226 | private static object readXml(XmlDocument xml) 227 | { 228 | XmlNode rootNode = xml.DocumentElement.ChildNodes[0]; 229 | return parse(rootNode); 230 | } 231 | 232 | private static object readBinary(byte[] data) 233 | { 234 | offsetTable.Clear(); 235 | List offsetTableBytes = new List(); 236 | objectTable.Clear(); 237 | refCount = 0; 238 | objRefSize = 0; 239 | offsetByteSize = 0; 240 | offsetTableOffset = 0; 241 | 242 | List bList = new List(data); 243 | 244 | List trailer = bList.GetRange(bList.Count - 32, 32); 245 | 246 | parseTrailer(trailer); 247 | 248 | objectTable = bList.GetRange(0, (int)offsetTableOffset); 249 | 250 | offsetTableBytes = bList.GetRange((int)offsetTableOffset, bList.Count - (int)offsetTableOffset - 32); 251 | 252 | parseOffsetTable(offsetTableBytes); 253 | 254 | return parseBinary(0); 255 | } 256 | 257 | private static Dictionary parseDictionary(XmlNode node) 258 | { 259 | XmlNodeList children = node.ChildNodes; 260 | if (children.Count % 2 != 0) 261 | { 262 | throw new DataMisalignedException("Dictionary elements must have an even number of child nodes"); 263 | } 264 | 265 | Dictionary dict = new Dictionary(); 266 | 267 | for (int i = 0; i < children.Count; i += 2) 268 | { 269 | XmlNode keynode = children[i]; 270 | XmlNode valnode = children[i + 1]; 271 | 272 | if (keynode.Name != "key") 273 | { 274 | throw new ApplicationException("expected a key node"); 275 | } 276 | 277 | object result = parse(valnode); 278 | 279 | if (result != null) 280 | { 281 | dict.Add(keynode.InnerText, result); 282 | } 283 | } 284 | 285 | return dict; 286 | } 287 | 288 | private static List parseArray(XmlNode node) 289 | { 290 | List array = new List(); 291 | 292 | foreach (XmlNode child in node.ChildNodes) 293 | { 294 | object result = parse(child); 295 | if (result != null) 296 | { 297 | array.Add(result); 298 | } 299 | } 300 | 301 | return array; 302 | } 303 | 304 | private static void composeArray(List value, XmlWriter writer) 305 | { 306 | writer.WriteStartElement("array"); 307 | foreach (object obj in value) 308 | { 309 | compose(obj, writer); 310 | } 311 | writer.WriteEndElement(); 312 | } 313 | 314 | private static object parse(XmlNode node) 315 | { 316 | switch (node.Name) 317 | { 318 | case "dict": 319 | return parseDictionary(node); 320 | case "array": 321 | return parseArray(node); 322 | case "string": 323 | return node.InnerText; 324 | case "integer": 325 | // int result; 326 | //int.TryParse(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo, out result); 327 | return Convert.ToInt32(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo); 328 | case "real": 329 | return Convert.ToDouble(node.InnerText,System.Globalization.NumberFormatInfo.InvariantInfo); 330 | case "false": 331 | return false; 332 | case "true": 333 | return true; 334 | case "null": 335 | return null; 336 | case "date": 337 | return XmlConvert.ToDateTime(node.InnerText, XmlDateTimeSerializationMode.Utc); 338 | case "data": 339 | return Convert.FromBase64String(node.InnerText); 340 | } 341 | 342 | throw new ApplicationException(String.Format("Plist Node `{0}' is not supported", node.Name)); 343 | } 344 | 345 | private static void compose(object value, XmlWriter writer) 346 | { 347 | 348 | if (value == null || value is string) 349 | { 350 | writer.WriteElementString("string", value as string); 351 | } 352 | else if (value is int || value is long) 353 | { 354 | writer.WriteElementString("integer", ((int)value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo)); 355 | } 356 | else if (value is System.Collections.Generic.Dictionary || 357 | value.GetType().ToString().StartsWith("System.Collections.Generic.Dictionary`2[System.String")) 358 | { 359 | //Convert to Dictionary 360 | Dictionary dic = value as Dictionary; 361 | if (dic == null) 362 | { 363 | dic = new Dictionary(); 364 | IDictionary idic = (IDictionary)value; 365 | foreach (var key in idic.Keys) 366 | { 367 | dic.Add(key.ToString(), idic[key]); 368 | } 369 | } 370 | writeDictionaryValues(dic, writer); 371 | } 372 | else if (value is List) 373 | { 374 | composeArray((List)value, writer); 375 | } 376 | else if (value is byte[]) 377 | { 378 | writer.WriteElementString("data", Convert.ToBase64String((Byte[])value)); 379 | } 380 | else if (value is float || value is double) 381 | { 382 | writer.WriteElementString("real", ((double)value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo)); 383 | } 384 | else if (value is DateTime) 385 | { 386 | DateTime time = (DateTime)value; 387 | string theString = XmlConvert.ToString(time, XmlDateTimeSerializationMode.Utc); 388 | writer.WriteElementString("date", theString);//, "yyyy-MM-ddTHH:mm:ssZ")); 389 | } 390 | else if (value is bool) 391 | { 392 | writer.WriteElementString(value.ToString().ToLower(), ""); 393 | } 394 | else 395 | { 396 | throw new Exception(String.Format("Value type '{0}' is unhandled", value.GetType().ToString())); 397 | } 398 | } 399 | 400 | private static void writeDictionaryValues(Dictionary dictionary, XmlWriter writer) 401 | { 402 | writer.WriteStartElement("dict"); 403 | foreach (string key in dictionary.Keys) 404 | { 405 | object value = dictionary[key]; 406 | writer.WriteElementString("key", key); 407 | compose(value, writer); 408 | } 409 | writer.WriteEndElement(); 410 | } 411 | 412 | private static int countObject(object value) 413 | { 414 | int count = 0; 415 | switch (value.GetType().ToString()) 416 | { 417 | case "System.Collections.Generic.Dictionary`2[System.String,System.Object]": 418 | Dictionary dict = (Dictionary)value; 419 | foreach (string key in dict.Keys) 420 | { 421 | count += countObject(dict[key]); 422 | } 423 | count += dict.Keys.Count; 424 | count++; 425 | break; 426 | case "System.Collections.Generic.List`1[System.Object]": 427 | List list = (List)value; 428 | foreach (object obj in list) 429 | { 430 | count += countObject(obj); 431 | } 432 | count++; 433 | break; 434 | default: 435 | count++; 436 | break; 437 | } 438 | return count; 439 | } 440 | 441 | private static byte[] writeBinaryDictionary(Dictionary dictionary) 442 | { 443 | List buffer = new List(); 444 | List header = new List(); 445 | List refs = new List(); 446 | for (int i = dictionary.Count - 1; i >= 0; i--) 447 | { 448 | var o = new object[dictionary.Count]; 449 | dictionary.Values.CopyTo(o, 0); 450 | composeBinary(o[i]); 451 | offsetTable.Add(objectTable.Count); 452 | refs.Add(refCount); 453 | refCount--; 454 | } 455 | for (int i = dictionary.Count - 1; i >= 0; i--) 456 | { 457 | var o = new string[dictionary.Count]; 458 | dictionary.Keys.CopyTo(o, 0); 459 | composeBinary(o[i]);//); 460 | offsetTable.Add(objectTable.Count); 461 | refs.Add(refCount); 462 | refCount--; 463 | } 464 | 465 | if (dictionary.Count < 15) 466 | { 467 | header.Add(Convert.ToByte(0xD0 | Convert.ToByte(dictionary.Count))); 468 | } 469 | else 470 | { 471 | header.Add(0xD0 | 0xf); 472 | header.AddRange(writeBinaryInteger(dictionary.Count, false)); 473 | } 474 | 475 | 476 | foreach (int val in refs) 477 | { 478 | byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize); 479 | Array.Reverse(refBuffer); 480 | buffer.InsertRange(0, refBuffer); 481 | } 482 | 483 | buffer.InsertRange(0, header); 484 | 485 | 486 | objectTable.InsertRange(0, buffer); 487 | 488 | return buffer.ToArray(); 489 | } 490 | 491 | private static byte[] composeBinaryArray(List objects) 492 | { 493 | List buffer = new List(); 494 | List header = new List(); 495 | List refs = new List(); 496 | 497 | for (int i = objects.Count - 1; i >= 0; i--) 498 | { 499 | composeBinary(objects[i]); 500 | offsetTable.Add(objectTable.Count); 501 | refs.Add(refCount); 502 | refCount--; 503 | } 504 | 505 | if (objects.Count < 15) 506 | { 507 | header.Add(Convert.ToByte(0xA0 | Convert.ToByte(objects.Count))); 508 | } 509 | else 510 | { 511 | header.Add(0xA0 | 0xf); 512 | header.AddRange(writeBinaryInteger(objects.Count, false)); 513 | } 514 | 515 | foreach (int val in refs) 516 | { 517 | byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize); 518 | Array.Reverse(refBuffer); 519 | buffer.InsertRange(0, refBuffer); 520 | } 521 | 522 | buffer.InsertRange(0, header); 523 | 524 | objectTable.InsertRange(0, buffer); 525 | 526 | return buffer.ToArray(); 527 | } 528 | 529 | private static byte[] composeBinary(object obj) 530 | { 531 | byte[] value; 532 | switch (obj.GetType().ToString()) 533 | { 534 | case "System.Collections.Generic.Dictionary`2[System.String,System.Object]": 535 | value = writeBinaryDictionary((Dictionary)obj); 536 | return value; 537 | 538 | case "System.Collections.Generic.List`1[System.Object]": 539 | value = composeBinaryArray((List)obj); 540 | return value; 541 | 542 | case "System.Byte[]": 543 | value = writeBinaryByteArray((byte[])obj); 544 | return value; 545 | 546 | case "System.Double": 547 | value = writeBinaryDouble((double)obj); 548 | return value; 549 | 550 | case "System.Int32": 551 | value = writeBinaryInteger((int)obj, true); 552 | return value; 553 | 554 | case "System.String": 555 | value = writeBinaryString((string)obj, true); 556 | return value; 557 | 558 | case "System.DateTime": 559 | value = writeBinaryDate((DateTime)obj); 560 | return value; 561 | 562 | case "System.Boolean": 563 | value = writeBinaryBool((bool)obj); 564 | return value; 565 | 566 | default: 567 | return new byte[0]; 568 | } 569 | } 570 | 571 | public static byte[] writeBinaryDate(DateTime obj) 572 | { 573 | List buffer =new List(RegulateNullBytes(BitConverter.GetBytes(PlistDateConverter.ConvertToAppleTimeStamp(obj)), 8)); 574 | buffer.Reverse(); 575 | buffer.Insert(0, 0x33); 576 | objectTable.InsertRange(0, buffer); 577 | return buffer.ToArray(); 578 | } 579 | 580 | public static byte[] writeBinaryBool(bool obj) 581 | { 582 | List buffer = new List(new byte[1] { (bool)obj ? (byte)9 : (byte)8 }); 583 | objectTable.InsertRange(0, buffer); 584 | return buffer.ToArray(); 585 | } 586 | 587 | private static byte[] writeBinaryInteger(int value, bool write) 588 | { 589 | List buffer = new List(BitConverter.GetBytes((long) value)); 590 | buffer =new List(RegulateNullBytes(buffer.ToArray())); 591 | while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2))) 592 | buffer.Add(0); 593 | int header = 0x10 | (int)(Math.Log(buffer.Count) / Math.Log(2)); 594 | 595 | buffer.Reverse(); 596 | 597 | buffer.Insert(0, Convert.ToByte(header)); 598 | 599 | if (write) 600 | objectTable.InsertRange(0, buffer); 601 | 602 | return buffer.ToArray(); 603 | } 604 | 605 | private static byte[] writeBinaryDouble(double value) 606 | { 607 | List buffer =new List(RegulateNullBytes(BitConverter.GetBytes(value), 4)); 608 | while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2))) 609 | buffer.Add(0); 610 | int header = 0x20 | (int)(Math.Log(buffer.Count) / Math.Log(2)); 611 | 612 | buffer.Reverse(); 613 | 614 | buffer.Insert(0, Convert.ToByte(header)); 615 | 616 | objectTable.InsertRange(0, buffer); 617 | 618 | return buffer.ToArray(); 619 | } 620 | 621 | private static byte[] writeBinaryByteArray(byte[] value) 622 | { 623 | List buffer = new List(value); 624 | List header = new List(); 625 | if (value.Length < 15) 626 | { 627 | header.Add(Convert.ToByte(0x40 | Convert.ToByte(value.Length))); 628 | } 629 | else 630 | { 631 | header.Add(0x40 | 0xf); 632 | header.AddRange(writeBinaryInteger(buffer.Count, false)); 633 | } 634 | 635 | buffer.InsertRange(0, header); 636 | 637 | objectTable.InsertRange(0, buffer); 638 | 639 | return buffer.ToArray(); 640 | } 641 | 642 | private static byte[] writeBinaryString(string value, bool head) 643 | { 644 | List buffer = new List(); 645 | List header = new List(); 646 | foreach (char chr in value.ToCharArray()) 647 | buffer.Add(Convert.ToByte(chr)); 648 | 649 | if (head) 650 | { 651 | if (value.Length < 15) 652 | { 653 | header.Add(Convert.ToByte(0x50 | Convert.ToByte(value.Length))); 654 | } 655 | else 656 | { 657 | header.Add(0x50 | 0xf); 658 | header.AddRange(writeBinaryInteger(buffer.Count, false)); 659 | } 660 | } 661 | 662 | buffer.InsertRange(0, header); 663 | 664 | objectTable.InsertRange(0, buffer); 665 | 666 | return buffer.ToArray(); 667 | } 668 | 669 | private static byte[] RegulateNullBytes(byte[] value) 670 | { 671 | return RegulateNullBytes(value, 1); 672 | } 673 | 674 | private static byte[] RegulateNullBytes(byte[] value, int minBytes) 675 | { 676 | Array.Reverse(value); 677 | List bytes = new List(value); 678 | for (int i = 0; i < bytes.Count; i++) 679 | { 680 | if (bytes[i] == 0 && bytes.Count > minBytes) 681 | { 682 | bytes.Remove(bytes[i]); 683 | i--; 684 | } 685 | else 686 | break; 687 | } 688 | 689 | if (bytes.Count < minBytes) 690 | { 691 | int dist = minBytes - bytes.Count; 692 | for (int i = 0; i < dist; i++) 693 | bytes.Insert(0, 0); 694 | } 695 | 696 | value = bytes.ToArray(); 697 | Array.Reverse(value); 698 | return value; 699 | } 700 | 701 | private static void parseTrailer(List trailer) 702 | { 703 | offsetByteSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(6, 1).ToArray(), 4), 0); 704 | objRefSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(7, 1).ToArray(), 4), 0); 705 | byte[] refCountBytes = trailer.GetRange(12, 4).ToArray(); 706 | Array.Reverse(refCountBytes); 707 | refCount = BitConverter.ToInt32(refCountBytes, 0); 708 | byte[] offsetTableOffsetBytes = trailer.GetRange(24, 8).ToArray(); 709 | Array.Reverse(offsetTableOffsetBytes); 710 | offsetTableOffset = BitConverter.ToInt64(offsetTableOffsetBytes, 0); 711 | } 712 | 713 | private static void parseOffsetTable(List offsetTableBytes) 714 | { 715 | for (int i = 0; i < offsetTableBytes.Count; i += offsetByteSize) 716 | { 717 | byte[] buffer = offsetTableBytes.GetRange(i, offsetByteSize).ToArray(); 718 | Array.Reverse(buffer); 719 | offsetTable.Add(BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0)); 720 | } 721 | } 722 | 723 | private static object parseBinaryDictionary(int objRef) 724 | { 725 | Dictionary buffer = new Dictionary(); 726 | List refs = new List(); 727 | int refCount = 0; 728 | 729 | int refStartPosition; 730 | refCount = getCount(offsetTable[objRef], out refStartPosition); 731 | 732 | 733 | if (refCount < 15) 734 | refStartPosition = offsetTable[objRef] + 1; 735 | else 736 | refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length; 737 | 738 | for (int i = refStartPosition; i < refStartPosition + refCount * 2 * objRefSize; i += objRefSize) 739 | { 740 | byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray(); 741 | Array.Reverse(refBuffer); 742 | refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0)); 743 | } 744 | 745 | for (int i = 0; i < refCount; i++) 746 | { 747 | buffer.Add((string)parseBinary(refs[i]), parseBinary(refs[i + refCount])); 748 | } 749 | 750 | return buffer; 751 | } 752 | 753 | private static object parseBinaryArray(int objRef) 754 | { 755 | List buffer = new List(); 756 | List refs = new List(); 757 | int refCount = 0; 758 | 759 | int refStartPosition; 760 | refCount = getCount(offsetTable[objRef], out refStartPosition); 761 | 762 | 763 | if (refCount < 15) 764 | refStartPosition = offsetTable[objRef] + 1; 765 | else 766 | //The following integer has a header aswell so we increase the refStartPosition by two to account for that. 767 | refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length; 768 | 769 | for (int i = refStartPosition; i < refStartPosition + refCount * objRefSize; i += objRefSize) 770 | { 771 | byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray(); 772 | Array.Reverse(refBuffer); 773 | refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0)); 774 | } 775 | 776 | for (int i = 0; i < refCount; i++) 777 | { 778 | buffer.Add(parseBinary(refs[i])); 779 | } 780 | 781 | return buffer; 782 | } 783 | 784 | private static int getCount(int bytePosition, out int newBytePosition) 785 | { 786 | byte headerByte = objectTable[bytePosition]; 787 | byte headerByteTrail = Convert.ToByte(headerByte & 0xf); 788 | int count; 789 | if (headerByteTrail < 15) 790 | { 791 | count = headerByteTrail; 792 | newBytePosition = bytePosition + 1; 793 | } 794 | else 795 | count = (int)parseBinaryInt(bytePosition + 1, out newBytePosition); 796 | return count; 797 | } 798 | 799 | private static object parseBinary(int objRef) 800 | { 801 | byte header = objectTable[offsetTable[objRef]]; 802 | switch (header & 0xF0) 803 | { 804 | case 0: 805 | { 806 | //If the byte is 807 | //0 return null 808 | //9 return true 809 | //8 return false 810 | return (objectTable[offsetTable[objRef]] == 0) ? (object)null : ((objectTable[offsetTable[objRef]] == 9) ? true : false); 811 | } 812 | case 0x10: 813 | { 814 | return parseBinaryInt(offsetTable[objRef]); 815 | } 816 | case 0x20: 817 | { 818 | return parseBinaryReal(offsetTable[objRef]); 819 | } 820 | case 0x30: 821 | { 822 | return parseBinaryDate(offsetTable[objRef]); 823 | } 824 | case 0x40: 825 | { 826 | return parseBinaryByteArray(offsetTable[objRef]); 827 | } 828 | case 0x50://String ASCII 829 | { 830 | return parseBinaryAsciiString(offsetTable[objRef]); 831 | } 832 | case 0x60://String Unicode 833 | { 834 | return parseBinaryUnicodeString(offsetTable[objRef]); 835 | } 836 | case 0xD0: 837 | { 838 | return parseBinaryDictionary(objRef); 839 | } 840 | case 0xA0: 841 | { 842 | return parseBinaryArray(objRef); 843 | } 844 | } 845 | throw new Exception("This type is not supported"); 846 | } 847 | 848 | public static object parseBinaryDate(int headerPosition) 849 | { 850 | byte[] buffer = objectTable.GetRange(headerPosition + 1, 8).ToArray(); 851 | Array.Reverse(buffer); 852 | double appleTime = BitConverter.ToDouble(buffer, 0); 853 | DateTime result = PlistDateConverter.ConvertFromAppleTimeStamp(appleTime); 854 | return result; 855 | } 856 | 857 | private static object parseBinaryInt(int headerPosition) 858 | { 859 | int output; 860 | return parseBinaryInt(headerPosition, out output); 861 | } 862 | 863 | private static object parseBinaryInt(int headerPosition, out int newHeaderPosition) 864 | { 865 | byte header = objectTable[headerPosition]; 866 | int byteCount = (int)Math.Pow(2, header & 0xf); 867 | byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray(); 868 | Array.Reverse(buffer); 869 | //Add one to account for the header byte 870 | newHeaderPosition = headerPosition + byteCount + 1; 871 | return BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0); 872 | } 873 | 874 | private static object parseBinaryReal(int headerPosition) 875 | { 876 | byte header = objectTable[headerPosition]; 877 | int byteCount = (int)Math.Pow(2, header & 0xf); 878 | byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray(); 879 | Array.Reverse(buffer); 880 | 881 | return BitConverter.ToDouble(RegulateNullBytes(buffer, 8), 0); 882 | } 883 | 884 | private static object parseBinaryAsciiString(int headerPosition) 885 | { 886 | int charStartPosition; 887 | int charCount = getCount(headerPosition, out charStartPosition); 888 | 889 | var buffer = objectTable.GetRange(charStartPosition, charCount); 890 | return buffer.Count > 0 ? Encoding.ASCII.GetString(buffer.ToArray()) : string.Empty; 891 | } 892 | 893 | private static object parseBinaryUnicodeString(int headerPosition) 894 | { 895 | int charStartPosition; 896 | int charCount = getCount(headerPosition, out charStartPosition); 897 | charCount = charCount * 2; 898 | 899 | byte[] buffer = new byte[charCount]; 900 | byte one, two; 901 | 902 | for (int i = 0; i < charCount; i+=2) 903 | { 904 | one = objectTable.GetRange(charStartPosition+i,1)[0]; 905 | two = objectTable.GetRange(charStartPosition + i+1, 1)[0]; 906 | 907 | if (BitConverter.IsLittleEndian) 908 | { 909 | buffer[i] = two; 910 | buffer[i + 1] = one; 911 | } 912 | else 913 | { 914 | buffer[i] = one; 915 | buffer[i + 1] = two; 916 | } 917 | } 918 | 919 | return Encoding.Unicode.GetString(buffer); 920 | } 921 | 922 | private static object parseBinaryByteArray(int headerPosition) 923 | { 924 | int byteStartPosition; 925 | int byteCount = getCount(headerPosition, out byteStartPosition); 926 | return objectTable.GetRange(byteStartPosition, byteCount).ToArray(); 927 | } 928 | 929 | #endregion 930 | } 931 | 932 | public enum plistType 933 | { 934 | Auto, Binary, Xml 935 | } 936 | 937 | public static class PlistDateConverter 938 | { 939 | public static long timeDifference = 978307200; 940 | 941 | public static long GetAppleTime(long unixTime) 942 | { 943 | return unixTime - timeDifference; 944 | } 945 | 946 | public static long GetUnixTime(long appleTime) 947 | { 948 | return appleTime + timeDifference; 949 | } 950 | 951 | public static DateTime ConvertFromAppleTimeStamp(double timestamp) 952 | { 953 | DateTime origin = new DateTime(2001, 1, 1, 0, 0, 0, 0); 954 | return origin.AddSeconds(timestamp); 955 | } 956 | 957 | public static double ConvertToAppleTimeStamp(DateTime date) 958 | { 959 | DateTime begin = new DateTime(2001, 1, 1, 0, 0, 0, 0); 960 | TimeSpan diff = date - begin; 961 | return Math.Floor(diff.TotalSeconds); 962 | } 963 | } 964 | } 965 | 966 | #endif 967 | 968 | --------------------------------------------------------------------------------