├── logs └── .gitignore ├── .gitignore ├── tools ├── bugly │ ├── buglySymboliOS.bat │ ├── buglySymboliOS.jar │ └── buglySymboliOS.sh └── bash │ ├── xcodebuild-safe.sh │ └── create_ipa.sh ├── plugins ├── Editor │ ├── XCodeEditor │ │ ├── PlistMod.cs │ │ ├── XCProject.cs │ │ ├── PBX Editor │ │ │ ├── PBXProject.cs │ │ │ ├── PBXList.cs │ │ │ ├── PBXSortedDictionary.cs │ │ │ ├── PBXBuildPhase.cs │ │ │ ├── PBXGroup.cs │ │ │ ├── PBXBuildFile.cs │ │ │ ├── PBXDictionary.cs │ │ │ ├── PBXObject.cs │ │ │ ├── PBXFileReference.cs │ │ │ └── PBXParser.cs │ │ ├── XCConfigurationList.cs │ │ ├── FixupFiles.cs │ │ ├── XCMod.cs │ │ ├── XCBuildConfiguration.cs │ │ └── MiniJSON │ │ │ └── MiniJSON.cs │ └── Deploy │ │ ├── XUnityDeployPostprocess.cs │ │ └── XUnityDeploy.cs └── ios │ └── plugins │ ├── XAlbumCameraController.h │ ├── XObject.m │ ├── XObject.h │ ├── XAnalytics.h │ ├── XAnalytics.mm │ ├── XGameCenter.h │ ├── XConfig.h │ ├── XAlbumCameraController.mm │ └── XGameCenter.mm ├── Gemfile ├── scripts ├── run_channel.rb ├── run_git_update.rb ├── run_svn_update.rb ├── run_bugly.rb ├── run_unity.rb ├── run_analyze_resource.rb ├── run_unity_anysdk.rb ├── run_unity_seabird.rb ├── run_bearychat.rb ├── run_unity_renchang.rb └── run_help.rb ├── utils ├── file_extend.rb ├── string_extend.rb ├── logger.rb ├── git_cmd.rb ├── channel_cmd.rb ├── bearychat_cmd.rb ├── anysdk_cmd.rb ├── svn_cmd.rb ├── unity_cmd.rb ├── optparse.rb ├── bugly_cmd.rb ├── utils.rb └── xcode_cmd.rb ├── unitys ├── unity.rb ├── compile_base.rb ├── analyze_resource.rb ├── compile_unity_anysdk.rb ├── compile_unity_seabird.rb ├── compile_unity.rb └── compile_unity_renchang.rb ├── Rakefile └── README.md /logs/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | builds/* 3 | jekins/* 4 | Gemfile.lock -------------------------------------------------------------------------------- /tools/bugly/buglySymboliOS.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcmHacker/XUnityDeploy/HEAD/tools/bugly/buglySymboliOS.bat -------------------------------------------------------------------------------- /tools/bugly/buglySymboliOS.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcmHacker/XUnityDeploy/HEAD/tools/bugly/buglySymboliOS.jar -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PlistMod.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcmHacker/XUnityDeploy/HEAD/plugins/Editor/XCodeEditor/PlistMod.cs -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/XCProject.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcmHacker/XUnityDeploy/HEAD/plugins/Editor/XCodeEditor/XCProject.cs -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXProject.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AcmHacker/XUnityDeploy/HEAD/plugins/Editor/XCodeEditor/PBX Editor/PBXProject.cs -------------------------------------------------------------------------------- /tools/bash/xcodebuild-safe.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash --login 2 | [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" 3 | rvm use system 4 | xcodebuild "$@" -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://gems.ruby-china.org' 2 | 3 | # xcodeproj 4 | gem 'xcodeproj' 5 | 6 | # bearychat 7 | # gem 'bearychat' 8 | gem 'bearychat-notifier' 9 | 10 | # fir 11 | gem 'fir-cli' 12 | -------------------------------------------------------------------------------- /scripts/run_channel.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 2 | include XUnityDeploy::Logger 3 | 4 | compile = XUnityDeploy::ChannelCmd.new 5 | compile.run ARGV.first 6 | -------------------------------------------------------------------------------- /scripts/run_git_update.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | git = XUnityDeploy::GitCmd.new 6 | git.run 7 | -------------------------------------------------------------------------------- /scripts/run_svn_update.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | svn = XUnityDeploy::SvnCmd.new 6 | svn.run 7 | -------------------------------------------------------------------------------- /scripts/run_bugly.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | compile = XUnityDeploy::BuglyCmd.new 6 | compile.build 7 | -------------------------------------------------------------------------------- /scripts/run_unity.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | compile = XUnityDeploy::CompileUnity.new 6 | compile.run 7 | -------------------------------------------------------------------------------- /scripts/run_analyze_resource.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | analyze = XUnityDeploy::AnalyzeResource.new 6 | analyze.run -------------------------------------------------------------------------------- /scripts/run_unity_anysdk.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | compile = XUnityDeploy::CompileUnityAnySDK.new 6 | compile.run 7 | -------------------------------------------------------------------------------- /scripts/run_unity_seabird.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | compile = XUnityDeploy::CompileUnitySeaBird.new 6 | compile.run 7 | -------------------------------------------------------------------------------- /scripts/run_bearychat.rb: -------------------------------------------------------------------------------- 1 | #enconding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | bearychat = XUnityDeploy::BearychatCmd.new 6 | bearychat.send ARGV.first -------------------------------------------------------------------------------- /scripts/run_unity_renchang.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + "/../utils/utils") 3 | include XUnityDeploy::Logger 4 | 5 | compile = XUnityDeploy::CompileUnityRenChang.new 6 | compile.run 7 | -------------------------------------------------------------------------------- /plugins/ios/plugins/XAlbumCameraController.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import "XObject.h" 4 | #import "XConfig.h" 5 | 6 | @interface XAlbumCameraController : UIViewController 7 | 8 | @end -------------------------------------------------------------------------------- /plugins/ios/plugins/XObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // NObject.m 3 | // Unity-iPhone 4 | // 5 | // Created by DreamTim on 7/8/14. 6 | // 7 | // 8 | 9 | #import "XObject.h" 10 | 11 | @implementation XObject 12 | 13 | - (void)initCreate 14 | { 15 | 16 | } 17 | @end 18 | -------------------------------------------------------------------------------- /plugins/ios/plugins/XObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // NObject.h 3 | // Unity-iPhone 4 | // 5 | // Created by DreamTim on 7/8/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface XObject : NSObject 12 | 13 | - (void)initCreate; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /tools/bash/create_ipa.sh: -------------------------------------------------------------------------------- 1 | # #!/bin/bash 2 | cd $1 3 | xcodebuild clean 4 | xcodebuild archive -project Unity-iPhone.xcodeproj -scheme Unity-iPhone -UseModernBuildSystem=NO -archivePath $3 5 | xcodebuild -exportArchive -exportOptionsPlist $2 -archivePath $3 -exportPath $4 6 | -------------------------------------------------------------------------------- /scripts/run_help.rb: -------------------------------------------------------------------------------- 1 | HELP =< 10 | #import "XObject.h" 11 | #import "XConfig.h" 12 | 13 | @interface XAnalytics : XObject 14 | 15 | XSHARE_INSTANCE(); 16 | 17 | @end -------------------------------------------------------------------------------- /utils/file_extend.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | class File 4 | def self.file_same? file_path1, file_path2 5 | # todo 6 | end 7 | 8 | def self.read_all file_name 9 | content = "" 10 | File.readlines(file_name).each do |line| 11 | content << line 12 | end 13 | content 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /plugins/ios/plugins/XAnalytics.mm: -------------------------------------------------------------------------------- 1 | // 2 | // XAnalytics.mm 3 | // Unity-iPhone 4 | // 5 | // Created by HQ on 14-1-7. 6 | // 7 | // 8 | 9 | #import "XAnalytics.h" 10 | #import "XConfig.h" 11 | // #import 12 | 13 | @implementation XAnalytics 14 | 15 | XCLASS_INSTANCE(XAnalytics) 16 | 17 | extern "C" 18 | { 19 | //初始化 20 | void _InitAnalytics() 21 | { 22 | // [FIRApp configure]; 23 | } 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/XCConfigurationList.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class XCConfigurationList : PBXObject 8 | { 9 | // XCBuildConfigurationList buildConfigurations; 10 | // bool defaultConfigurationIsVisible = false; 11 | // string defaultConfigurationName; 12 | 13 | public XCConfigurationList( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugins/ios/plugins/XGameCenter.h: -------------------------------------------------------------------------------- 1 | // 2 | // XGameCenter.h 3 | // kp 4 | // 5 | // Created by DreamTim on 12/7/15. 6 | // Copyright © 2015 cmjstudio. All rights reserved. 7 | // 8 | 9 | #ifndef XGameCenter_h 10 | #define XGameCenter_h 11 | #import 12 | 13 | @interface XGameCenter : NSObject 14 | 15 | + (instancetype)shareInstance; 16 | 17 | - (void)openGameCenter; 18 | - (void)authorize; 19 | 20 | //成就接口,其中value[0-100],100表示完成了此成就 21 | -(void)addAchievementWithIdentifier:(NSString *)identifier percent:(double)value; 22 | //排行榜分数接口 23 | -(void)addScoreWithIdentifier:(NSString *)identifier score:(int64_t)value; 24 | @end 25 | 26 | #endif /* XGameCenter_h */ 27 | -------------------------------------------------------------------------------- /utils/string_extend.rb: -------------------------------------------------------------------------------- 1 | #extend string some methods 2 | class String 3 | def logger 4 | XUnityDeploy.logger 5 | end 6 | 7 | def sys_call 8 | `#{self}` 9 | logger.error("sys call '#{self}' has error") unless $?.success? 10 | true if $?.success? 11 | end 12 | 13 | def sys_call_with_log 14 | system(self) 15 | logger.error("sys call '#{self}' has error") unless $?.success? 16 | true if $?.success? 17 | end 18 | 19 | def sys_call_with_result 20 | results = `#{self}`.chomp 21 | logger.error("sys call_with_result '#{self}' has error") unless $?.success? 22 | results if $?.success? 23 | end 24 | end 25 | 26 | -------------------------------------------------------------------------------- /utils/logger.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'logger' 3 | module XUnityDeploy 4 | module Logger 5 | def logger 6 | @logger ||= begin 7 | # log type 8 | @logger = ::Logger.new(STDOUT) 9 | 10 | # log level 11 | @logger.level = ::Logger::DEBUG 12 | 13 | @logger.datetime_format = "%Y-%m-%d %H:%M:%S" 14 | 15 | # add log exception 16 | def @logger.exception exception, info 17 | self.error("#{info} and exception trace : \n #{exception.backtrace.join("\n")}") 18 | end 19 | @logger 20 | end 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /unitys/unity.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | module XUnityDeploy 3 | # compile base 4 | autoload :CompileBase, File.join(File.dirname(__FILE__), "compile_base") 5 | # compile unity 6 | autoload :CompileUnity, File.join(File.dirname(__FILE__), "compile_unity") 7 | # compile unity anysdk 8 | autoload :CompileUnityAnySDK, File.join(File.dirname(__FILE__), "compile_unity_anysdk") 9 | 10 | # compile unity ren chang 11 | autoload :CompileUnityRenChang, File.join(File.dirname(__FILE__), "compile_unity_renchang") 12 | 13 | # compile unity sea brid 14 | autoload :CompileUnitySeaBird, File.join(File.dirname(__FILE__), "compile_unity_seabird") 15 | 16 | # analyze resource 17 | autoload :AnalyzeResource, File.join(File.dirname(__FILE__), "analyze_resource") 18 | end -------------------------------------------------------------------------------- /utils/git_cmd.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | module XUnityDeploy 4 | class GitCmd 5 | 6 | def run 7 | check 8 | 9 | # pull 10 | 11 | status 12 | end 13 | 14 | private 15 | def check 16 | cmd = "git --version" 17 | raise "git cmd not exist." unless cmd.sys_call 18 | 19 | cmd = "git status" 20 | raise "git repository not exist" unless cmd.sys_call 21 | end 22 | 23 | def pull 24 | cmd = "git pull" 25 | raise "git pull error" unless cmd.sys_call 26 | end 27 | 28 | def status 29 | logger.info("**" * 10 + " git list " + "**" * 10) 30 | cmd = "git status" 31 | raise "git status error" unless cmd.sys_call_with_log 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /utils/channel_cmd.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | require 'fileutils' 3 | 4 | module XUnityDeploy 5 | class ChannelCmd 6 | 7 | def run channel_name 8 | # check 9 | raise "the channel #{channel_name} is invalid" unless check(channel_name) 10 | 11 | # delete ConfigPath 12 | FileUtils.rm_rf(ConfigPath) 13 | 14 | # create ConfigPath 15 | Dir.mkdir(ConfigPath) 16 | 17 | # copy ChannelPath (channel config) to ConfigPath 18 | FileUtils.cp_r(File.join(ChannelPath, channel_name, "XUnityDeploy_configs"), UnityProjectPath) 19 | end 20 | 21 | private 22 | def check channel_name 23 | return channel_name != nil && Dir.exists?(File.join(ChannelPath, channel_name, "XUnityDeploy_configs")) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /utils/bearychat_cmd.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | # require 'bearychat' 3 | require 'bearychat-notifier' 4 | 5 | module XUnityDeploy 6 | class BearychatCmd 7 | 8 | def send msg 9 | if hook_url_exist? then 10 | # incoming = Bearychat.incoming(get_hook_url) 11 | # incoming.send({:text => msg}) 12 | 13 | Bearychat::Notifier.new(get_hook_url).ping(msg) 14 | else 15 | logger.warn("hook_url not exist.") 16 | end 17 | end 18 | 19 | def get_hook_url 20 | # path = File.expand_path("~") + "/.unitydeploy.bearychat" 21 | # raise "hook url = nil in #{path}" unless File.exists?(path) 22 | return File.read(get_hook_path).strip 23 | end 24 | 25 | def hook_url_exist? 26 | return File.exists?(get_hook_path) 27 | end 28 | 29 | def get_hook_path 30 | return File.expand_path("~") + "/.unitydeploy.bearychat" 31 | end 32 | end 33 | 34 | end -------------------------------------------------------------------------------- /utils/anysdk_cmd.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | # call anysdk command to build anysdk project 4 | module XUnityDeploy 5 | class AnySDKCmd 6 | # TODO check unity environment 7 | def check 8 | end 9 | 10 | def build 11 | anysdk = "/Applications/AnySDK.app/Contents/MacOS/command" 12 | 13 | config_path = File.join(ConfigPath, "unity_deploy.json") 14 | config = JSON.parse (File.read_all(config_path)) 15 | platform_config = config[DeployOptions[:platform].to_s] 16 | 17 | game_name = platform_config["ProductName"] 18 | channel = platform_config["Channel"] 19 | if DeployOptions[:platform] == :android then 20 | apk_file = "#{BuildPath}/android.apk" 21 | cmd = "#{anysdk} run -game '#{game_name}' -channel #{channel} -platform 0 -apkfile '#{apk_file}'" 22 | else 23 | ios_project_path = File.join(BuildPath, "ios", "Unity-iPhone.xcodeproj") 24 | cmd = "#{anysdk} run -game '#{game_name}' -channel #{channel} -platform 1 -projfile '#{ios_project_path}'" 25 | end 26 | raise "AnySDK build Error" unless cmd.sys_call_with_log 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXList.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class PBXList : ArrayList 8 | { 9 | public PBXList() 10 | { 11 | 12 | } 13 | 14 | public PBXList( object firstValue ) 15 | { 16 | this.Add( firstValue ); 17 | } 18 | 19 | /// 20 | /// This allows us to use the form: 21 | /// "if (x)" or "if (!x)" 22 | /// 23 | public static implicit operator bool( PBXList x ) { 24 | //if null or empty, treat us as false/null 25 | return (x == null) ? false : (x.Count == 0); 26 | } 27 | 28 | /// 29 | /// I find this handy. return our fields as comma-separated values 30 | /// 31 | public string ToCSV() { 32 | // TODO use a char sep argument to allow specifying separator 33 | string ret = string.Empty; 34 | foreach (string item in this) { 35 | ret += "\""; 36 | ret += item; 37 | ret += "\", "; 38 | } 39 | return ret; 40 | } 41 | 42 | /// 43 | /// Concatenate and format so appears as "{,,,}" 44 | /// 45 | public override string ToString() { 46 | return "{" + this.ToCSV() + "} "; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /utils/svn_cmd.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | module XUnityDeploy 4 | class SvnCmd 5 | 6 | def run 7 | Dir.chdir(UnityProjectPath) 8 | 9 | check 10 | 11 | update 12 | 13 | status 14 | end 15 | 16 | private 17 | # check svn info 18 | def check 19 | cmd = "svn --version" 20 | raise "svn cmd not exist." unless cmd.sys_call 21 | 22 | cmd = "svn info" 23 | raise "svn or svn working copy is not exist." unless cmd.sys_call 24 | end 25 | 26 | # svn update 27 | def update 28 | # # del files by svn status is ? 29 | # cmd = "svn st | grep '?' | awk '{print $2}' | grep ^Assets/ | xargs -I x rm -rf x" 30 | # raise 'remove ? error!' unless cmd.sys_call 31 | 32 | # svn up 33 | cmd = "svn up --accept 'theirs-full'" 34 | raise "svn up error!" unless cmd.sys_call 35 | end 36 | 37 | #svn status 38 | def status 39 | logger.info("**" * 10 + " svn list " + "**" * 10) 40 | cmd = "svn st" 41 | raise "svn st error!" unless cmd.sys_call 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /unitys/compile_base.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | module XUnityDeploy 4 | class CompileBase 5 | # game name 6 | attr_accessor :game_name 7 | # channel 8 | attr_accessor :channel 9 | def initialize 10 | config_path = File.join(ConfigPath, "unity_deploy.json") 11 | config = JSON.parse (File.read_all(config_path)) 12 | platform_config = config[DeployOptions[:platform].to_s] 13 | 14 | @game_name = platform_config["ProductName"] 15 | @channel = platform_config["Channel"] 16 | end 17 | 18 | # Subclass implementation 19 | def run 20 | logger.info("Start Compile Unity Project(#{DeployOptions[:platform]}) ...") 21 | 22 | init_compile_config 23 | 24 | compile_ios if DeployOptions[:platform] == :ios 25 | 26 | compile_android if DeployOptions[:platform] == :android 27 | end 28 | 29 | protected 30 | # Set compile config in client 31 | def init_compile_config 32 | end 33 | 34 | # Subclass implementation 35 | def compile_android 36 | end 37 | 38 | # Subclass implementation 39 | def compile_ios 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /utils/unity_cmd.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | # call unity command to build unity project 4 | module XUnityDeploy 5 | class UnityCmd 6 | def initialize method 7 | @method = method 8 | end 9 | 10 | def build 11 | project_path = UnityProjectPath 12 | 13 | # get unity command path by env yaml 14 | env = {} 15 | env = YAML.load_file(EnvPath) if File.exist? EnvPath 16 | unity = env["unity3d"] ||= "/Applications/Unity/Unity.app/Contents/MacOS/Unity" 17 | log_file = env["logFile"] ||= "/dev/stdout" 18 | # unity = "/Applications/Unity/Unity.app/Contents/MacOS/Unity" 19 | if DeployOptions[:is_log] then 20 | log_path = File.join(LogPath, "deploy_#{DeployOptions[:platform]}.log") 21 | cmd = "#{unity} -batchmode -executeMethod #{@method} -quit -logFile #{log_file} -projectPath #{project_path} | tee #{log_path}" 22 | else 23 | cmd = "#{unity} -batchmode -executeMethod #{@method} -quit -logFile #{log_file} -projectPath #{project_path}" 24 | end 25 | cmd.sys_call_with_log 26 | end 27 | 28 | # TODO check unity environment 29 | def check 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /plugins/ios/plugins/XConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // XConfig.h 3 | // Unity-iPhone 4 | // 5 | // Created by DreamTim on 5/23/14. 6 | // 7 | // 8 | 9 | #ifndef Unity_iPhone_XConfig_h 10 | #define Unity_iPhone_XConfig_h 11 | 12 | #define ALog(format, ...) NSLog((@"%s [L%d] " format), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) 13 | 14 | //#ifndef RELEASE 15 | //#define XLog(format, ...) ALog(format, ##__VA_ARGS__) 16 | //#else 17 | //#define XLog(...) 18 | //#endif 19 | #ifndef RELEASE 20 | #define XLog(format, ...) 21 | #else 22 | #define XLog(...) 23 | #endif 24 | 25 | #define PSTRING(str) [NSString stringWithUTF8String:str] 26 | #define PSTRING2(value) [NSString stringWithFormat:@"%@", value] 27 | 28 | //注册class 29 | #define XREGIST_CLASS(ClassNameInstance) \ 30 | @implementation XPlatform (XPlatformImpl) \ 31 | - (void) initCreate \ 32 | { \ 33 | ClassNameInstance; \ 34 | } \ 35 | @end \ 36 | 37 | //定义shareInstance 38 | #define XSHARE_INSTANCE() \ 39 | + (instancetype)shareInstance; 40 | 41 | //定义init(这个可以不用c#层调用,就可以init) 42 | #define XCALL_INIT(ClassName) \ 43 | static ClassName* _instance = [ClassName shareInstance]; 44 | 45 | //类单例的宏 46 | #define XCLASS_INSTANCE(ClassName) \ 47 | + (instancetype)shareInstance \ 48 | { \ 49 | static ClassName *_kshareInstance = nil; \ 50 | static dispatch_once_t onceToken; \ 51 | dispatch_once(&onceToken, ^{ \ 52 | _kshareInstance = [[ClassName alloc] init]; \ 53 | [_kshareInstance initCreate]; \ 54 | }); \ 55 | return _kshareInstance; \ 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /unitys/analyze_resource.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | module XUnityDeploy 4 | class AnalyzeResource 5 | 6 | def run 7 | resources = read_resources 8 | 9 | analyze_resources(resources) 10 | end 11 | 12 | private 13 | def read_resources 14 | resources = [] 15 | File.open("#{LogPath}/deploy_#{DeployOptions[:platform].to_s}.log").each do |line| 16 | if /% Assets/ =~ line then 17 | path = line.sub(/\n/, '').split(/ /)[4..-1].join(' ') 18 | resources << path 19 | end 20 | end 21 | resources 22 | end 23 | 24 | def analyze_resources resources 25 | # clear 26 | FileUtils.remove_entry("#{LogPath}/Assets", true) 27 | Dir::mkdir "#{LogPath}/Assets" 28 | 29 | resources.each do |path| 30 | begin 31 | copy_files path 32 | rescue Errno::ENOENT 33 | logger.error("copy #{path} failed") 34 | end 35 | end 36 | end 37 | 38 | def copy_files path 39 | source_path = File.join(UnityProjectPath, path) 40 | index = path.rindex('/') 41 | path = "#{LogPath}/#{path[0..index]}" 42 | FileUtils.mkdir_p path 43 | FileUtils.cp_r source_path, path 44 | end 45 | 46 | end 47 | end -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXSortedDictionary.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class PBXSortedDictionary : SortedDictionary 8 | { 9 | 10 | public void Append( PBXDictionary dictionary ) 11 | { 12 | foreach( var item in dictionary) { 13 | this.Add( item.Key, item.Value ); 14 | } 15 | } 16 | 17 | public void Append( PBXDictionary dictionary ) where T : PBXObject 18 | { 19 | foreach( var item in dictionary) { 20 | this.Add( item.Key, item.Value ); 21 | } 22 | } 23 | } 24 | 25 | public class PBXSortedDictionary : SortedDictionary where T : PBXObject 26 | { 27 | public PBXSortedDictionary() 28 | { 29 | 30 | } 31 | 32 | public PBXSortedDictionary( PBXDictionary genericDictionary ) 33 | { 34 | foreach( KeyValuePair currentItem in genericDictionary ) { 35 | if( ((string)((PBXDictionary)currentItem.Value)[ "isa" ]).CompareTo( typeof(T).Name ) == 0 ) { 36 | T instance = (T)System.Activator.CreateInstance( typeof(T), currentItem.Key, (PBXDictionary)currentItem.Value ); 37 | this.Add( currentItem.Key, instance ); 38 | } 39 | } 40 | } 41 | 42 | public void Add( T newObject ) 43 | { 44 | this.Add( newObject.guid, newObject ); 45 | } 46 | 47 | public void Append( PBXDictionary dictionary ) 48 | { 49 | foreach( KeyValuePair item in dictionary) { 50 | this.Add( item.Key, (T)item.Value ); 51 | } 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /unitys/compile_unity_anysdk.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | require 'xcodeproj' 3 | 4 | module XUnityDeploy 5 | class CompileUnityAnySDK < CompileUnity 6 | 7 | protected 8 | def compile_android 9 | super 10 | 11 | anysdk = AnySDKCmd.new 12 | anysdk.build 13 | end 14 | 15 | def compile_ios 16 | compile_ios_before 17 | 18 | compile_unity 19 | 20 | remove_ios_target 21 | 22 | anysdk = AnySDKCmd.new 23 | anysdk.build 24 | 25 | remove_ios_scheme 26 | 27 | xcode = XUnityDeploy::XCodeCmd.new "Unity-iPhone-#{channel}" 28 | xcode.run 29 | end 30 | 31 | def remove_ios_target 32 | ios_project_path = File.join(BuildPath, "ios", "Unity-iPhone.xcodeproj") 33 | proj = ::Xcodeproj::Project.open(ios_project_path) 34 | targets = proj.targets 35 | if targets.length == 2 then 36 | logger.info("删除Test Target") 37 | targets[1].remove_from_project 38 | proj.save 39 | end 40 | end 41 | 42 | def remove_ios_scheme 43 | ios_project_path = File.join(BuildPath, "ios", "Unity-iPhone-#{channel}.xcodeproj") 44 | proj = Xcodeproj::Project.open(ios_project_path) 45 | scheme = Xcodeproj::XCScheme.new(ios_project_path + '/xcshareddata/xcschemes/Unity-iPhone.xcscheme') 46 | scheme.add_build_target(proj.targets[0]) 47 | scheme.save! 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /utils/optparse.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'optparse' 3 | 4 | # deploy options 5 | DeployOptions = { 6 | # 平台 7 | :platform => :ios, 8 | # 目标 9 | :target => "", 10 | # :client_mode => :develop, 11 | :channel_name => :official, 12 | :is_log => false, 13 | } 14 | 15 | ARGV.clone.options do |opts| 16 | # platform 17 | opts.on("-p", "--platform=name", String, 18 | "Specifies the client platform (ios/android).", 19 | "Default: ios" 20 | ) do |v| 21 | raise "platform param '#{v}' error." unless [:ios, :android].include?(v.to_sym) 22 | DeployOptions[:platform] = v.to_sym 23 | end 24 | 25 | # target 26 | opts.on("-t", "--target=name", String, 27 | "Specifies the client target.", 28 | "Default: ios" 29 | ) do |v| 30 | DeployOptions[:target] = v 31 | end 32 | 33 | #client mode 34 | # opts.on("-c", "--client_mode=mode", String, 35 | # "Specifies the client mode (develop/business/publish).", 36 | # "Default: develop" 37 | # ) do |v| 38 | # raise "client_mode param '#{v}' error." unless [:develop, :business, :publish].include?(v.to_sym) 39 | # DeployOptions[:client_mode] = v 40 | # end 41 | 42 | #is log 43 | opts.on("-l", "--is_log=open", TrueClass, 44 | "open or close log to configs/ (true/false).", 45 | "Default: false" 46 | ) do |v| 47 | DeployOptions[:is_log] = v 48 | end 49 | 50 | opts.separator "" 51 | 52 | opts.on("-h", "--help", "Show this help message.") do 53 | puts opts 54 | exit 55 | end 56 | 57 | opts.parse! 58 | end 59 | 60 | puts "Start XUnityDeploy ..." 61 | -------------------------------------------------------------------------------- /utils/bugly_cmd.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | require 'json' 3 | require 'plist' 4 | 5 | # call unity command to build unity project 6 | module XUnityDeploy 7 | class BuglyCmd 8 | def initialize 9 | end 10 | 11 | # TODO check unity environment 12 | def check 13 | end 14 | 15 | def build 16 | path = File.join(ConfigPath, "bugly.json") 17 | if not File.exists? (path) then 18 | puts "bugly.json not exists" 19 | return; 20 | end 21 | 22 | 23 | project_path = UnityProjectPath 24 | bugly = File.join(ToolPath, "bugly", "buglySymboliOS.jar") 25 | dysm_path = File.join(BuildPath, "ios.xcarchive", "dSYMs", "cannon.app.dSYM") 26 | info_plist = Plist.parse_xml(File.join(BuildPath, "ios.xcarchive", "Info.plist")) 27 | info_property = info_plist["ApplicationProperties"] 28 | 29 | 30 | # deploy_config = JSON.parse(File.read(File.join(ConfigPath, "unity_deploy.json"))) 31 | # main_version = deploy_config["ios"]["Version"] 32 | # build_version = File.read(File.join(UnityProjectPath, "Assets", "Resources", "info.txt")) 33 | # version = main_version + "." + build_version 34 | version = info_property["CFBundleShortVersionString"] + "." + info_property["CFBundleVersion"] 35 | package_name = info_property["CFBundleIdentifier"] 36 | 37 | bugly_config = JSON.parse(File.read(File.join(ConfigPath, "bugly.json"))) 38 | 39 | cmd = "java -jar #{bugly} -i #{dysm_path} -u -id #{bugly_config["appId"]} -key #{bugly_config["appKey"]} -package #{package_name} -version #{version}" 40 | # puts cmd 41 | cmd.sys_call_with_log 42 | end 43 | 44 | end 45 | end -------------------------------------------------------------------------------- /tools/bugly/buglySymboliOS.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2015-2016 Bugly, Tencent Inc. All rights reserved. 4 | # 5 | # Usage: 6 | # buglySymboliOS.sh [out.zip] 7 | # 8 | # Extract symbols from dSYM file. 9 | # It call 'buglySymboliOS.jar', make sure there is Java environment on the system. 10 | # 11 | # 12 | 13 | function printIndroduction { 14 | echo "Bugly符号表工具IOS版 -- Bugly Symtab Tool for IOS" 15 | echo "适用平台 -- Applicable platform: Linux" 16 | echo "Copyright 2015-2016 Bugly, Tencent Inc. All rights reserved." 17 | echo "" 18 | } 19 | 20 | # function - usage 21 | function printUsage(){ 22 | echo "----" 23 | echo "$1" 24 | echo "----" 25 | echo "用法 -- Usage: buglySymboliOS.sh [out.zip]" 26 | echo "" 27 | echo "参数说明 -- Introduction for arguments" 28 | echo ":" 29 | echo " dSYM文件路径名 -- Path where dSYM file exist" 30 | echo "" 31 | echo "[out.zip] (可选 -- Optional):" 32 | echo " 输出文件名 -- Zip file name for output" 33 | echo " 如果[out.zip]不是绝对路径名,[out.zip]将生成在脚本所在目录" 34 | echo " -- If it's not an absolute path, the [out.zip] will be generated into the directory of the script." 35 | exit 3 36 | } 37 | 38 | # function - extract 39 | function extractSymbol() { 40 | java -Xms512m -Xmx1024m -Dfile.encoding=UTF8 -jar "$JarPath" -i $* 41 | } 42 | 43 | # main 44 | printIndroduction 45 | 46 | # Check the Java Environment 47 | CheckJavaVersion=$(java -version 2>&1) 48 | echo "$CheckJavaVersion" | grep -q "Java(TM)" 49 | if [ $? -ne 0 ] 50 | then 51 | echo "----" 52 | echo "系统中未安装Java或者未配置Java环境,请检查!-- Please check if your system has installed Java or configured environment for Java!" 53 | echo "Java官网 -- Java Web Site:www.java.com" 54 | exit 1 55 | fi 56 | 57 | # Check the jar 58 | #ShellDir=$(cd `dirname $0`; pwd) 59 | pathName=$(cd `dirname $0`; pwd) 60 | JarName="buglySymboliOS.jar" 61 | JarPath="$pathName/$JarName" 62 | if [ ! -f "$JarPath" ]; then 63 | echo "----" 64 | echo "未找到\"$JarName\"!-- Can not find \"$JarName\"!" 65 | echo "请将\"$JarName\"复制到\"$pathName\"中!" 66 | echo " -- Please copy \"$JarName\" to \"$pathName\"!" 67 | exit 2 68 | fi 69 | 70 | # call the function to extract symbols 71 | extractSymbol $* -------------------------------------------------------------------------------- /utils/utils.rb: -------------------------------------------------------------------------------- 1 | 2 | # require 3rd lib 3 | require 'yaml' 4 | require 'json' 5 | require 'fileutils' 6 | require 'pathname' 7 | 8 | #untiy project path 9 | UnityProjectPath = Pathname.new(File.dirname(__FILE__)).join("../../").cleanpath 10 | 11 | #deploy unity path, 12 | DeployProjectPath = File.join(UnityProjectPath, "XUnityDeploy") 13 | 14 | #deploy config file path 15 | ConfigPath = File.join(UnityProjectPath, "XUnityDeploy_configs") 16 | 17 | #deploy channel files path 18 | ChannelPath = File.join(UnityProjectPath, "XUnityDeploy_channels") 19 | 20 | #deploy build path 21 | BuildPath = File.join(DeployProjectPath, "builds") 22 | 23 | #deploy logs path 24 | LogPath = File.join(DeployProjectPath, "logs") 25 | 26 | #deploy tools path 27 | ToolPath = File.join(DeployProjectPath, "tools") 28 | 29 | #deploy env config path 30 | EnvPath = File.join(ConfigPath, ".env.yaml") 31 | 32 | # require XUnityDeploy Scripts 33 | require File.expand_path(File.join(DeployProjectPath, "utils/optparse")) 34 | require File.expand_path(File.join(DeployProjectPath, "utils/logger")) 35 | require File.expand_path(File.join(DeployProjectPath, "utils/file_extend")) 36 | require File.expand_path(File.join(DeployProjectPath, "utils/string_extend")) 37 | require File.expand_path(File.join(DeployProjectPath, "utils/unity_cmd")) 38 | require File.expand_path(File.join(DeployProjectPath, "utils/xcode_cmd")) 39 | require File.expand_path(File.join(DeployProjectPath, "utils/svn_cmd")) 40 | require File.expand_path(File.join(DeployProjectPath, "utils/git_cmd")) 41 | require File.expand_path(File.join(DeployProjectPath, "utils/anysdk_cmd")) 42 | require File.expand_path(File.join(DeployProjectPath, "utils/bearychat_cmd")) 43 | require File.expand_path(File.join(DeployProjectPath, "utils/bugly_cmd")) 44 | require File.expand_path(File.join(DeployProjectPath, "utils/channel_cmd")) 45 | 46 | require File.expand_path(File.join(DeployProjectPath, "unitys/unity")) 47 | 48 | module XUnityDeploy 49 | module Utils 50 | extend self 51 | 52 | # load config yml files 53 | def load_config name 54 | path = File.join(DeployConfig, name + ".yml") 55 | return YAML::load(File.open(path)) 56 | end 57 | end 58 | 59 | def version 60 | "1.0" 61 | end 62 | end -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXBuildPhase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class PBXBuildPhase : PBXObject 8 | { 9 | protected const string FILES_KEY = "files"; 10 | 11 | public PBXBuildPhase() :base() 12 | { 13 | } 14 | 15 | public PBXBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 16 | { 17 | } 18 | 19 | public bool AddBuildFile( PBXBuildFile file ) 20 | { 21 | if( !ContainsKey( FILES_KEY ) ){ 22 | this.Add( FILES_KEY, new PBXList() ); 23 | } 24 | ((PBXList)_data[ FILES_KEY ]).Add( file.guid ); 25 | return true; 26 | } 27 | 28 | public void RemoveBuildFile( string id ) 29 | { 30 | if( !ContainsKey( FILES_KEY ) ) { 31 | this.Add( FILES_KEY, new PBXList() ); 32 | return; 33 | } 34 | 35 | ((PBXList)_data[ FILES_KEY ]).Remove( id ); 36 | } 37 | 38 | public bool HasBuildFile( string id ) 39 | { 40 | if( !ContainsKey( FILES_KEY ) ) { 41 | this.Add( FILES_KEY, new PBXList() ); 42 | return false; 43 | } 44 | 45 | if( !IsGuid( id ) ) 46 | return false; 47 | 48 | return ((PBXList)_data[ FILES_KEY ]).Contains( id ); 49 | } 50 | 51 | public PBXList files { 52 | get { 53 | if( !ContainsKey( FILES_KEY ) ) { 54 | this.Add( FILES_KEY, new PBXList() ); 55 | } 56 | return (PBXList)_data[ FILES_KEY ]; 57 | } 58 | } 59 | } 60 | 61 | public class PBXFrameworksBuildPhase : PBXBuildPhase 62 | { 63 | public PBXFrameworksBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 64 | { 65 | } 66 | } 67 | 68 | public class PBXResourcesBuildPhase : PBXBuildPhase 69 | { 70 | public PBXResourcesBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 71 | { 72 | } 73 | } 74 | 75 | public class PBXShellScriptBuildPhase : PBXBuildPhase 76 | { 77 | public PBXShellScriptBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 78 | { 79 | } 80 | } 81 | 82 | public class PBXSourcesBuildPhase : PBXBuildPhase 83 | { 84 | public PBXSourcesBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 85 | { 86 | } 87 | } 88 | 89 | public class PBXCopyFilesBuildPhase : PBXBuildPhase 90 | { 91 | public PBXCopyFilesBuildPhase( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 92 | { 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /unitys/compile_unity_seabird.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | module XUnityDeploy 4 | class CompileUnitySeaBird 5 | # ignore super 6 | def initialize 7 | raise "pls set target." if DeployOptions[:target] == "" 8 | 9 | if build_ios? then 10 | DeployOptions[:platform] = :ios 11 | else 12 | DeployOptions[:platform] = :android 13 | end 14 | end 15 | 16 | def run 17 | logger.info("Start Compile Unity (#{DeployOptions[:target]}) ...") 18 | 19 | # rm xcode 20 | if build_ios? then 21 | xcode_path = File.join(UnityProjectPath, "builds", "xcode") 22 | FileUtils.remove_entry(xcode_path, true) 23 | 24 | ipa_path = File.join(UnityProjectPath, "builds", "IPA") 25 | FileUtils.remove_entry(ipa_path, true) 26 | else 27 | # rm apk 28 | apk_path = File.join(UnityProjectPath, "builds", "*.apk") 29 | Dir[apk_path].each {|filename| FileUtils.remove_file(filename, true) } 30 | aab_path = File.join(UnityProjectPath, "builds", "*.aab") 31 | Dir[aab_path].each {|filename| FileUtils.remove_file(filename, true) } 32 | end 33 | 34 | do_compile 35 | 36 | # ios build xcode 37 | if build_ios? then 38 | do_compile_xcode 39 | end 40 | end 41 | 42 | # private 43 | def build_ios? 44 | return DeployOptions[:target].include?("IOS") 45 | end 46 | 47 | def do_compile 48 | cmd = UnityCmd.new "XUnityDeploy.#{DeployOptions[:target]}" 49 | raise "*** Compile #{DeployOptions[:target]} failed!" unless cmd.build 50 | end 51 | 52 | def do_compile_xcode 53 | shell_path = File.join(ToolPath, "bash", "create_ipa.sh") 54 | xcode_path = File.join(UnityProjectPath, "builds", "xcode") 55 | plist_path = File.join(UnityProjectPath, "configs", "packagePList.plist") 56 | xcarchive_path = File.join(UnityProjectPath, "builds", "xcode", "ship.xcarchive") 57 | ipa_path = File.join(UnityProjectPath, "builds", "IPA") 58 | cmd = "/bin/bash #{shell_path} #{xcode_path} #{plist_path} #{xcarchive_path} #{ipa_path}" 59 | raise "*** Compile Xcode error" unless cmd.sys_call_with_log 60 | end 61 | end 62 | end -------------------------------------------------------------------------------- /unitys/compile_unity.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | module XUnityDeploy 4 | class CompileUnity < CompileBase 5 | def initialize 6 | super 7 | end 8 | 9 | protected 10 | def compile_android 11 | compile_android_before 12 | 13 | compile_unity 14 | 15 | compile_android_after 16 | end 17 | 18 | def compile_ios 19 | compile_ios_before 20 | 21 | compile_unity 22 | 23 | xcode = XUnityDeploy::XCodeCmd.new "Unity-iPhone" 24 | xcode.run 25 | end 26 | 27 | def compile_unity 28 | cmd = UnityCmd.new get_unity_method 29 | 30 | raise "***Compile #{DeployOptions[:platform]} failed!" unless cmd.build 31 | # raise "***Scan Compile #{@platform} log error!" if scan_error? 32 | end 33 | 34 | # do things before compile android 35 | # such as copy files or delete files 36 | def compile_android_before 37 | # TODO 38 | end 39 | 40 | # clear symbols.zip 41 | def compile_android_after 42 | Dir[BuildPath + "/android-*.zip"].each do |file| 43 | FileUtils.rm file 44 | end 45 | end 46 | 47 | def compile_ios_before 48 | # remove old project path 49 | path = File.join(BuildPath, "ios") 50 | FileUtils.remove_entry(path, true) 51 | 52 | path = File.join(BuildPath, "ios.xcarchive") 53 | FileUtils.remove_entry(path, true) 54 | 55 | end 56 | 57 | # generate unity config file and use them in compile unity 58 | def init_compile_config 59 | # TODO 60 | end 61 | 62 | def get_unity_method 63 | if DeployOptions[:platform] == :ios 64 | return "XUnityDeploy.BuildIOS" 65 | else 66 | return "XUnityDeploy.BuildAndroid" 67 | end 68 | end 69 | 70 | def system_capabilities proj_path, config_hash 71 | proj = ::Xcodeproj::Project.open(proj_path) 72 | uuid = proj.targets.first.uuid 73 | capabilities = proj.root_object.attributes['TargetAttributes'][uuid]['SystemCapabilities'] 74 | 75 | config_hash.each do |key, value| 76 | if value then 77 | capabilities[key] = {"enabled" => "1"} 78 | else 79 | capabilities[key] = {"enabled" => "0"} 80 | end 81 | end 82 | 83 | proj.save 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXGroup.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class PBXGroup : PBXObject 8 | { 9 | protected const string NAME_KEY = "name"; 10 | protected const string CHILDREN_KEY = "children"; 11 | protected const string PATH_KEY = "path"; 12 | protected const string SOURCETREE_KEY = "sourceTree"; 13 | 14 | #region Constructor 15 | 16 | public PBXGroup( string name, string path = null, string tree = "SOURCE_ROOT" ) : base() 17 | { 18 | this.Add( CHILDREN_KEY, new PBXList() ); 19 | this.Add( NAME_KEY, name ); 20 | 21 | if( path != null ) { 22 | this.Add( PATH_KEY, path ); 23 | this.Add( SOURCETREE_KEY, tree ); 24 | } 25 | else { 26 | this.Add( SOURCETREE_KEY, "" ); 27 | } 28 | } 29 | 30 | public PBXGroup( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) 31 | { 32 | 33 | } 34 | 35 | #endregion 36 | #region Properties 37 | 38 | public PBXList children { 39 | get { 40 | if( !ContainsKey( CHILDREN_KEY ) ) { 41 | this.Add( CHILDREN_KEY, new PBXList() ); 42 | } 43 | return (PBXList)_data[CHILDREN_KEY]; 44 | } 45 | } 46 | 47 | public string name { 48 | get { 49 | if( !ContainsKey( NAME_KEY ) ) { 50 | return null; 51 | } 52 | return (string)_data[NAME_KEY]; 53 | } 54 | } 55 | 56 | public string path { 57 | get { 58 | if( !ContainsKey( PATH_KEY ) ) { 59 | return null; 60 | } 61 | return (string)_data[PATH_KEY]; 62 | } 63 | } 64 | 65 | public string sourceTree { 66 | get { 67 | return (string)_data[SOURCETREE_KEY]; 68 | } 69 | } 70 | 71 | #endregion 72 | 73 | 74 | public string AddChild( PBXObject child ) 75 | { 76 | if( child is PBXFileReference || child is PBXGroup ) { 77 | children.Add( child.guid ); 78 | return child.guid; 79 | } 80 | 81 | return null; 82 | } 83 | 84 | public void RemoveChild( string id ) 85 | { 86 | if( !IsGuid( id ) ) 87 | return; 88 | 89 | children.Remove( id ); 90 | } 91 | 92 | public bool HasChild( string id ) 93 | { 94 | if( !ContainsKey( CHILDREN_KEY ) ) { 95 | this.Add( CHILDREN_KEY, new PBXList() ); 96 | return false; 97 | } 98 | 99 | if( !IsGuid( id ) ) 100 | return false; 101 | 102 | return ((PBXList)_data[ CHILDREN_KEY ]).Contains( id ); 103 | } 104 | 105 | public string GetName() 106 | { 107 | return (string)_data[ NAME_KEY ]; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /unitys/compile_unity_renchang.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | require 'xcodeproj' 3 | 4 | module XUnityDeploy 5 | class CompileUnityRenChang < CompileUnity 6 | 7 | protected 8 | def compile_android 9 | # copy firebase configs 10 | # copy_firebase_configs 11 | 12 | super 13 | end 14 | 15 | def compile_ios 16 | compile_ios_before 17 | 18 | compile_unity 19 | 20 | #config 21 | config_capabilities 22 | 23 | # add Pods.xcodeproj 24 | # add_pods_to_unity 25 | 26 | xcode = XUnityDeploy::XCodeCmd.new "Unity-iPhone" 27 | xcode.run 28 | end 29 | 30 | def copy_firebase_configs 31 | script_path = File.join(DeployProjectPath, 'tools', 'Firebase', 'generate_xml_from_google_services_json.py') 32 | in_path = File.join(ConfigPath, 'firebase', 'google-services.json') 33 | out_path = File.join(UnityProjectPath, 'Assets', 'Plugins', 'Android', 'Firebase', 'res', 'values', 'googleservices.xml') 34 | 35 | cmd = "python #{script_path} -i #{in_path} -o #{out_path}" 36 | "copy firebase configs error" unless cmd.sys_call 37 | 38 | out_path = File.join(UnityProjectPath, 'Assets', 'Plugins', 'Android', 'Firebase', 'res', 'values', 'google-services.xml') 39 | 40 | cmd = "python #{script_path} -i #{in_path} -o #{out_path}" 41 | "copy firebase configs error2" unless cmd.sys_call 42 | end 43 | 44 | # add Pods.xcodeproj to unity 45 | def add_pods_to_unity 46 | ios_project_path = File.join(BuildPath, "ios", "Unity-iPhone.xcodeproj") 47 | proj = ::Xcodeproj::Project.open(ios_project_path) 48 | 49 | pods_project_path = File.join(BuildPath, "ios", "Pods", "Pods.xcodeproj") 50 | proj.new_file(pods_project_path) 51 | 52 | proj.save 53 | end 54 | 55 | def config_capabilities 56 | proj_path = File.join(BuildPath, "ios", "Unity-iPhone.xcodeproj") 57 | config_path = File.join(ConfigPath, "ios", "main.build.json") 58 | 59 | config = JSON.parse (File.read_all(config_path)) 60 | 61 | config['projmods'].each do |item| 62 | projmod_path = File.join(ConfigPath, "ios", item) 63 | projmods = JSON.parse(File.read_all(projmod_path)) 64 | 65 | capabilities = projmods['system_capabilities'] 66 | 67 | if capabilities then 68 | system_capabilities(proj_path, capabilities) 69 | end 70 | end 71 | 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /plugins/Editor/Deploy/XUnityDeployPostprocess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEditor; 4 | using UnityEditor.Callbacks; 5 | using UnityEditor.XCodeEditor; 6 | using System.Collections.Generic; 7 | using System.Collections; 8 | 9 | using XUPorterJSON; 10 | using System.IO; 11 | 12 | namespace UnityEditor.XUnityDeploy 13 | { 14 | public static class XUnityDeployPostProcess 15 | { 16 | delegate void AddData(Hashtable root, string key); 17 | 18 | [PostProcessBuild(200)] 19 | public static void OnPostProcessBuild(BuildTarget target, string path) 20 | { 21 | Debug.Log("XUnityDeploy Post Process ...."); 22 | if (target == BuildTarget.iOS) 23 | { 24 | var project = new UnityEditor.XCodeEditor.XCProject(path); 25 | 26 | var xcodeConfigPath = Path.Combine(Application.dataPath.Replace("Assets", ""), "XUnityDeploy/configs/xcode"); 27 | 28 | var buildFilePath = Path.Combine(xcodeConfigPath, "main.build.json"); 29 | Debug.Log("buildPath : " + buildFilePath); 30 | string buildContent = System.IO.File.ReadAllText(buildFilePath); 31 | 32 | var buildData = MiniJSON.jsonDecode(buildContent) as Hashtable; 33 | var projmodList = buildData["projmods"] as ArrayList; 34 | 35 | // Find and run through all projmods files to patch the project 36 | // string projModPath = System.IO.Path.Combine(Application.dataPath, "Editor/iOS"); 37 | var projModPath = xcodeConfigPath; 38 | for (int index = 0; index < projmodList.Count; ++index) 39 | { 40 | var file = System.IO.Path.Combine(projModPath, projmodList[index].ToString()); 41 | Debug.Log(String.Format("Project ApplyMode : {0}", file)); 42 | try 43 | { 44 | project.ApplyMod(file); 45 | } 46 | catch (System.Exception ex) 47 | { 48 | //log 49 | Debug.LogError("Project ApplyMode Exception : " + ex.Message + ex.StackTrace); 50 | throw ex; 51 | } 52 | 53 | } 54 | 55 | project.Save(); 56 | 57 | var info = buildData["info"] as string; 58 | var infoPath = System.IO.Path.Combine(projModPath, info); 59 | var infoContent = System.IO.File.ReadAllText(infoPath); 60 | var infoData = MiniJSON.jsonDecode(infoContent) as Hashtable; 61 | 62 | PlistMod.UpdatePlist_Extend(path, infoData); 63 | } 64 | 65 | if (target == BuildTarget.Android) 66 | { 67 | //TODO 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXBuildFile.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class PBXBuildFile : PBXObject 8 | { 9 | private const string FILE_REF_KEY = "fileRef"; 10 | private const string SETTINGS_KEY = "settings"; 11 | private const string ATTRIBUTES_KEY = "ATTRIBUTES"; 12 | private const string WEAK_VALUE = "Weak"; 13 | private const string COMPILER_FLAGS_KEY = "COMPILER_FLAGS"; 14 | 15 | public PBXBuildFile( PBXFileReference fileRef, bool weak = false ) : base() 16 | { 17 | this.Add( FILE_REF_KEY, fileRef.guid ); 18 | SetWeakLink( weak ); 19 | } 20 | 21 | public PBXBuildFile( string guid, PBXDictionary dictionary ) : base ( guid, dictionary ) 22 | { 23 | } 24 | 25 | public string fileRef 26 | { 27 | get { 28 | return (string)_data[ FILE_REF_KEY ]; 29 | } 30 | } 31 | 32 | public bool SetWeakLink( bool weak = false ) 33 | { 34 | PBXDictionary settings = null; 35 | PBXList attributes = null; 36 | 37 | if( !_data.ContainsKey( SETTINGS_KEY ) ) { 38 | if( weak ) { 39 | attributes = new PBXList(); 40 | attributes.Add( WEAK_VALUE ); 41 | 42 | settings = new PBXDictionary(); 43 | settings.Add( ATTRIBUTES_KEY, attributes ); 44 | 45 | _data.Add( SETTINGS_KEY, settings ); 46 | } 47 | return true; 48 | } 49 | 50 | settings = _data[ SETTINGS_KEY ] as PBXDictionary; 51 | if( !settings.ContainsKey( ATTRIBUTES_KEY ) ) { 52 | if( weak ) { 53 | attributes = new PBXList(); 54 | attributes.Add( WEAK_VALUE ); 55 | settings.Add( ATTRIBUTES_KEY, attributes ); 56 | return true; 57 | } 58 | else { 59 | return false; 60 | } 61 | } 62 | else { 63 | attributes = settings[ ATTRIBUTES_KEY ] as PBXList; 64 | } 65 | 66 | if( weak ) { 67 | attributes.Add( WEAK_VALUE ); 68 | } 69 | else { 70 | attributes.Remove( WEAK_VALUE ); 71 | } 72 | 73 | settings.Add( ATTRIBUTES_KEY, attributes ); 74 | this.Add( SETTINGS_KEY, settings ); 75 | 76 | return true; 77 | } 78 | 79 | public bool AddCompilerFlag( string flag ) 80 | { 81 | if( !_data.ContainsKey( SETTINGS_KEY ) ) 82 | _data[ SETTINGS_KEY ] = new PBXDictionary(); 83 | 84 | if( !((PBXDictionary)_data[ SETTINGS_KEY ]).ContainsKey( COMPILER_FLAGS_KEY ) ) { 85 | ((PBXDictionary)_data[ SETTINGS_KEY ]).Add( COMPILER_FLAGS_KEY, flag ); 86 | return true; 87 | } 88 | 89 | string[] flags = ((string)((PBXDictionary)_data[ SETTINGS_KEY ])[ COMPILER_FLAGS_KEY ]).Split( ' ' ); 90 | foreach( string item in flags ) { 91 | if( item.CompareTo( flag ) == 0 ) 92 | return false; 93 | } 94 | 95 | ((PBXDictionary)_data[ SETTINGS_KEY ])[ COMPILER_FLAGS_KEY ] = ( string.Join( " ", flags ) + " " + flag ); 96 | return true; 97 | } 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXDictionary.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class PBXDictionary : Dictionary 8 | { 9 | 10 | public void Append( PBXDictionary dictionary ) 11 | { 12 | foreach( var item in dictionary) { 13 | this.Add( item.Key, item.Value ); 14 | } 15 | } 16 | 17 | public void Append( PBXDictionary dictionary ) where T : PBXObject 18 | { 19 | foreach( var item in dictionary) { 20 | this.Add( item.Key, item.Value ); 21 | } 22 | } 23 | 24 | public void Append( PBXSortedDictionary dictionary ) 25 | { 26 | foreach( var item in dictionary) { 27 | this.Add( item.Key, item.Value ); 28 | } 29 | } 30 | 31 | public void Append( PBXSortedDictionary dictionary ) where T : PBXObject 32 | { 33 | foreach( var item in dictionary) { 34 | this.Add( item.Key, item.Value ); 35 | } 36 | } 37 | 38 | /// 39 | /// This allows us to use the form: 40 | /// "if (x)" or "if (!x)" 41 | /// 42 | public static implicit operator bool( PBXDictionary x ) { 43 | //if null or empty, treat us as false/null 44 | return (x == null) ? false : (x.Count == 0); 45 | } 46 | 47 | /// 48 | /// I find this handy. return our fields as comma-separated values 49 | /// 50 | public string ToCSV() { 51 | // TODO use a char sep argument to allow specifying separator 52 | string ret = string.Empty; 53 | foreach (KeyValuePair item in this) { 54 | ret += "<"; 55 | ret += item.Key; 56 | ret += ", "; 57 | ret += item.Value; 58 | ret += ">, "; 59 | } 60 | return ret; 61 | } 62 | 63 | /// 64 | /// Concatenate and format so appears as "{,,,}" 65 | /// 66 | public override string ToString() { 67 | return "{" + this.ToCSV() + "}"; 68 | } 69 | 70 | } 71 | 72 | public class PBXDictionary : Dictionary where T : PBXObject 73 | { 74 | public PBXDictionary() 75 | { 76 | 77 | } 78 | 79 | public PBXDictionary( PBXDictionary genericDictionary ) 80 | { 81 | foreach( KeyValuePair currentItem in genericDictionary ) { 82 | if( ((string)((PBXDictionary)currentItem.Value)[ "isa" ]).CompareTo( typeof(T).Name ) == 0 ) { 83 | T instance = (T)System.Activator.CreateInstance( typeof(T), currentItem.Key, (PBXDictionary)currentItem.Value ); 84 | this.Add( currentItem.Key, instance ); 85 | } 86 | } 87 | } 88 | 89 | public PBXDictionary( PBXSortedDictionary genericDictionary ) 90 | { 91 | foreach( KeyValuePair currentItem in genericDictionary ) { 92 | if( ((string)((PBXDictionary)currentItem.Value)[ "isa" ]).CompareTo( typeof(T).Name ) == 0 ) { 93 | T instance = (T)System.Activator.CreateInstance( typeof(T), currentItem.Key, (PBXDictionary)currentItem.Value ); 94 | this.Add( currentItem.Key, instance ); 95 | } 96 | } 97 | } 98 | 99 | public void Add( T newObject ) 100 | { 101 | this.Add( newObject.guid, newObject ); 102 | } 103 | 104 | public void Append( PBXDictionary dictionary ) 105 | { 106 | foreach( KeyValuePair item in dictionary) { 107 | this.Add( item.Key, (T)item.Value ); 108 | } 109 | } 110 | 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/FixupFiles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.RegularExpressions; 4 | using UnityEngine; 5 | using UnityEditor; 6 | 7 | namespace UnityEditor.NKMEditor 8 | { 9 | public class FixupFiles 10 | { 11 | protected static string Load(string fullPath) 12 | { 13 | string data; 14 | FileInfo projectFileInfo = new FileInfo( fullPath ); 15 | StreamReader fs = projectFileInfo.OpenText(); 16 | data = fs.ReadToEnd(); 17 | fs.Close(); 18 | 19 | return data; 20 | } 21 | 22 | protected static void Save(string fullPath, string data) 23 | { 24 | System.IO.StreamWriter writer = new System.IO.StreamWriter(fullPath, false); 25 | writer.Write(data); 26 | writer.Close(); 27 | } 28 | 29 | public static void FixSimulator(string path) 30 | { 31 | string fullPath = Path.Combine(path, Path.Combine("Libraries", "RegisterMonoModules.cpp")); 32 | string data = Load (fullPath); 33 | 34 | 35 | data = Regex.Replace(data, @"\s+void\s+mono_dl_register_symbol\s+\(const\s+char\*\s+name,\s+void\s+\*addr\);", ""); 36 | data = Regex.Replace(data, "typedef int gboolean;", "typedef int gboolean;\n\tvoid mono_dl_register_symbol (const char* name, void *addr);"); 37 | 38 | data = Regex.Replace(data, @"#endif\s+//\s*!\s*\(\s*TARGET_IPHONE_SIMULATOR\s*\)\s*}\s*void RegisterAllStrippedInternalCalls\s*\(\s*\)", "}\n\nvoid RegisterAllStrippedInternalCalls()"); 39 | data = Regex.Replace(data, @"mono_aot_register_module\(mono_aot_module_mscorlib_info\);", 40 | "mono_aot_register_module(mono_aot_module_mscorlib_info);\n#endif // !(TARGET_IPHONE_SIMULATOR)"); 41 | 42 | Save (fullPath, data); 43 | } 44 | 45 | public static void AddVersionDefine(string path) 46 | { 47 | int versionNumber = GetUnityVersionNumber (); 48 | 49 | string fullPath = Path.Combine(path, Path.Combine("Libraries", "RegisterMonoModules.h")); 50 | string data = Load(fullPath); 51 | 52 | if (versionNumber >= 430) 53 | { 54 | data += "\n#define HAS_UNITY_VERSION_DEF 1\n"; 55 | } else { 56 | data += "\n#define UNITY_VERSION "; 57 | data += versionNumber; 58 | data += "\n"; 59 | } 60 | 61 | Save(fullPath, data); 62 | } 63 | 64 | private static int GetUnityVersionNumber() 65 | { 66 | string version = Application.unityVersion; 67 | string[] versionComponents = version.Split('.'); 68 | 69 | int majorVersion = 0; 70 | int minorVersion = 0; 71 | 72 | try 73 | { 74 | if (versionComponents != null && versionComponents.Length > 0 && versionComponents[0] != null) 75 | majorVersion = Convert.ToInt32(versionComponents[0]); 76 | if (versionComponents != null && versionComponents.Length > 1 && versionComponents[1] != null) 77 | minorVersion = Convert.ToInt32(versionComponents[1]); 78 | } 79 | catch (System.Exception e) 80 | { 81 | Debug.LogError("Error parsing Unity version number: " + e); 82 | } 83 | 84 | return ((majorVersion * 100) + (minorVersion * 10)); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/XCMod.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.IO; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class XCMod 8 | { 9 | private Hashtable _datastore = new Hashtable(); 10 | private ArrayList _libs = null; 11 | 12 | public string name { get; private set; } 13 | public string path { get; private set; } 14 | 15 | public Hashtable DataStore 16 | { 17 | get 18 | { 19 | return _datastore; 20 | } 21 | } 22 | public string group { 23 | get { 24 | if (_datastore != null && _datastore.Contains("group")) 25 | return (string)_datastore["group"]; 26 | return string.Empty; 27 | } 28 | } 29 | 30 | public ArrayList patches { 31 | get { 32 | return (ArrayList)_datastore["patches"]; 33 | } 34 | } 35 | 36 | public ArrayList libs { 37 | get { 38 | if( _libs == null ) { 39 | _libs = new ArrayList( ((ArrayList)_datastore["libs"]).Count ); 40 | foreach( string fileRef in (ArrayList)_datastore["libs"] ) { 41 | _libs.Add( new XCModFile( fileRef ) ); 42 | } 43 | } 44 | return _libs; 45 | } 46 | } 47 | 48 | public ArrayList frameworks { 49 | get { 50 | return (ArrayList)_datastore["frameworks"]; 51 | } 52 | } 53 | 54 | public ArrayList headerpaths { 55 | get { 56 | return (ArrayList)_datastore["headerpaths"]; 57 | } 58 | } 59 | 60 | public ArrayList files { 61 | get { 62 | return (ArrayList)_datastore["files"]; 63 | } 64 | } 65 | 66 | public ArrayList folders { 67 | get { 68 | return (ArrayList)_datastore["folders"]; 69 | } 70 | } 71 | 72 | public ArrayList excludes { 73 | get { 74 | return (ArrayList)_datastore["excludes"]; 75 | } 76 | } 77 | 78 | public ArrayList compiler_flags { 79 | get { 80 | return (ArrayList)_datastore["compiler_flags"]; 81 | } 82 | } 83 | 84 | public ArrayList linker_flags { 85 | get { 86 | return (ArrayList)_datastore["linker_flags"]; 87 | } 88 | } 89 | 90 | //tim add "build_setting" 91 | public Hashtable build_settings 92 | { 93 | get { return (Hashtable)_datastore["build_settings"]; } 94 | } 95 | 96 | public XCMod( string filename ) 97 | { 98 | FileInfo projectFileInfo = new FileInfo( filename ); 99 | if( !projectFileInfo.Exists ) { 100 | Debug.LogWarning( "File does not exist." ); 101 | } 102 | 103 | name = System.IO.Path.GetFileNameWithoutExtension( filename ); 104 | path = System.IO.Path.GetDirectoryName( filename ); 105 | 106 | string contents = projectFileInfo.OpenText().ReadToEnd(); 107 | //Debug.Log (contents); 108 | _datastore = (Hashtable)XUPorterJSON.MiniJSON.jsonDecode( contents ); 109 | if (_datastore == null || _datastore.Count == 0) { 110 | Debug.Log (contents); 111 | throw new UnityException("Parse error in file " + System.IO.Path.GetFileName(filename) + "! Check for typos such as unbalanced quotation marks, etc."); 112 | } 113 | } 114 | } 115 | 116 | public class XCModFile 117 | { 118 | public string filePath { get; private set; } 119 | public bool isWeak { get; private set; } 120 | 121 | public XCModFile( string inputString ) 122 | { 123 | isWeak = false; 124 | 125 | if( inputString.Contains( ":" ) ) { 126 | string[] parts = inputString.Split( ':' ); 127 | filePath = parts[0]; 128 | isWeak = ( parts[1].CompareTo( "weak" ) == 0 ); 129 | } 130 | else { 131 | filePath = inputString; 132 | } 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /utils/xcode_cmd.rb: -------------------------------------------------------------------------------- 1 | #encoding = utf-8 2 | 3 | module XUnityDeploy 4 | class XCodeCmd 5 | 6 | def initialize project_name 7 | # xcworkspace or xcodeproj directory is exist 8 | if (File.directory?(File.join(BuildPath, "ios", project_name + ".xcworkspace"))) 9 | project_name = project_name + ".xcworkspace" 10 | else 11 | project_name = project_name + ".xcodeproj" 12 | end 13 | @project_path = File.join(BuildPath, "ios", project_name) 14 | @xarchive_path = File.join(BuildPath, "ios.xcarchive") 15 | @ipa_path = File.join(BuildPath, "ios.ipa") 16 | @export_plist = File.join(ConfigPath, "export.plist") 17 | 18 | config_path = File.join(ConfigPath, "ios", "main.projmods.json") 19 | config = JSON.parse (File.read_all(config_path)) 20 | @xcode_sign_identify = config['build_settings']['CODE_SIGN_IDENTITY'] 21 | 22 | @xcodebuild_safe = File.join(ToolPath, "bash", "xcodebuild-safe.sh") 23 | 24 | # add sign 25 | unlock_cmd = unlock_keychain_cmd 26 | @xcodebuild_safe = unlock_cmd + " & " + @xcodebuild_safe if !unlock_cmd.empty? 27 | end 28 | 29 | def run 30 | # build xcode 31 | build 32 | 33 | #sign ipa 34 | sign 35 | end 36 | 37 | private 38 | def build 39 | # remove old files 40 | 41 | FileUtils.remove_entry(@xarchive_path, true) 42 | FileUtils.remove_entry(@ipa_path, true) 43 | 44 | # cmd = "#{@xcodebuild_safe} -project #{@project_path} -scheme 'Unity-iPhone' archive -archivePath #{@xarchive_path}" 45 | cmd = "" 46 | # workspace 47 | if (@project_path.end_with?(".xcworkspace")) then 48 | cmd = "#{@xcodebuild_safe} -workspace #{@project_path} -scheme 'Unity-iPhone' archive -archivePath #{@xarchive_path}" 49 | else 50 | # project 51 | cmd = "#{@xcodebuild_safe} -project #{@project_path} -scheme 'Unity-iPhone' archive -archivePath #{@xarchive_path}" 52 | end 53 | 54 | if cmd.sys_call_with_log or File.exist?(@xarchive_path) then 55 | logger.info("xcode build ok.") 56 | else 57 | raise "xcode build error" 58 | end 59 | end 60 | 61 | def sign 62 | ipa_dir_path = BuildPath 63 | cmd = "#{@xcodebuild_safe} -exportArchive -archivePath #{@xarchive_path} -exportPath #{ipa_dir_path} -exportOptionsPlist #{@export_plist}" 64 | raise "xcodebuild export error!" unless cmd.sys_call_with_log 65 | 66 | #rename 67 | File.rename(File.join(ipa_dir_path, ipa_name), @ipa_path) 68 | end 69 | 70 | def ipa_name 71 | env = {} 72 | env = YAML.load_file(EnvPath) if File.exist? EnvPath 73 | name = env["ipa_name"] || "Unity-iPhone.ipa" 74 | return name 75 | end 76 | 77 | def unlock_keychain_cmd 78 | env = {} 79 | env = YAML.load_file(EnvPath) if File.exist? EnvPath 80 | passwd = env["system_passwd"] || "" 81 | if passwd.empty? then 82 | return "" 83 | else 84 | return "security unlock-keychain -p '#{passwd}' ~/Library/Keychains/login.keychain" 85 | end 86 | end 87 | 88 | def xcode_version 89 | version = "xcodebuild -version | grep 'Xcode' | awk '{print $2}'".sys_call_with_result 90 | end 91 | 92 | # TODO check xcode environment 93 | def check 94 | 95 | end 96 | end 97 | end 98 | 99 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXObject.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class PBXObject 8 | { 9 | protected const string ISA_KEY = "isa"; 10 | 11 | protected string _guid; 12 | protected PBXDictionary _data; 13 | 14 | #region Properties 15 | 16 | public string guid { 17 | get { 18 | if( string.IsNullOrEmpty( _guid ) ) 19 | _guid = GenerateGuid(); 20 | 21 | return _guid; 22 | } 23 | } 24 | 25 | public PBXDictionary data { 26 | get { 27 | if( _data == null ) 28 | _data = new PBXDictionary(); 29 | 30 | return _data; 31 | } 32 | } 33 | 34 | 35 | #endregion 36 | #region Constructors 37 | 38 | public PBXObject() 39 | { 40 | _data = new PBXDictionary(); 41 | _data[ ISA_KEY ] = this.GetType().Name; 42 | _guid = GenerateGuid(); 43 | } 44 | 45 | public PBXObject( string guid ) : this() 46 | { 47 | if( IsGuid( guid ) ) 48 | _guid = guid; 49 | } 50 | 51 | public PBXObject( string guid, PBXDictionary dictionary ) : this( guid ) 52 | { 53 | if( !dictionary.ContainsKey( ISA_KEY ) || ((string)dictionary[ ISA_KEY ]).CompareTo( this.GetType().Name ) != 0 ) 54 | Debug.LogError( "PBXDictionary is not a valid ISA object" ); 55 | 56 | foreach( KeyValuePair item in dictionary ) { 57 | _data[ item.Key ] = item.Value; 58 | } 59 | } 60 | 61 | #endregion 62 | #region Static methods 63 | 64 | public static bool IsGuid( string aString ) 65 | { 66 | // Note: Unity3d generates mixed-case GUIDs, Xcode use uppercase GUIDs only. 67 | return System.Text.RegularExpressions.Regex.IsMatch( aString, @"^[A-Fa-f0-9]{24}$" ); 68 | } 69 | 70 | public static string GenerateGuid() 71 | { 72 | return System.Guid.NewGuid().ToString("N").Substring( 8 ).ToUpper(); 73 | } 74 | 75 | 76 | #endregion 77 | #region Data manipulation 78 | 79 | public void Add( string key, object obj ) 80 | { 81 | _data.Add( key, obj ); 82 | } 83 | 84 | public bool Remove( string key ) 85 | { 86 | return _data.Remove( key ); 87 | } 88 | 89 | public bool ContainsKey( string key ) 90 | { 91 | return _data.ContainsKey( key ); 92 | } 93 | 94 | #endregion 95 | #region syntactic sugar 96 | /// 97 | /// This allows us to use the form: 98 | /// "if (x)" or "if (!x)" 99 | /// 100 | public static implicit operator bool( PBXObject x ) { 101 | //if null or no data, treat us as false/null 102 | return (x == null) ? false : (x.data.Count == 0); 103 | } 104 | 105 | /// 106 | /// I find this handy. return our fields as comma-separated values 107 | /// 108 | public string ToCSV() { 109 | return "\"" + data + "\", "; 110 | } 111 | 112 | /// 113 | /// Concatenate and format so appears as "{,,,}" 114 | /// 115 | public override string ToString() { 116 | return "{" + this.ToCSV() + "} "; 117 | } 118 | #endregion 119 | } 120 | 121 | public class PBXNativeTarget : PBXObject 122 | { 123 | public PBXNativeTarget() : base() { 124 | } 125 | 126 | public PBXNativeTarget( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 127 | } 128 | 129 | //tim add name 130 | public string Name 131 | { 132 | get 133 | { 134 | string name = _data["name"] as string; 135 | return name; 136 | } 137 | } 138 | } 139 | 140 | public class PBXContainerItemProxy : PBXObject 141 | { 142 | public PBXContainerItemProxy() : base() { 143 | } 144 | 145 | public PBXContainerItemProxy( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 146 | } 147 | } 148 | 149 | public class PBXReferenceProxy : PBXObject 150 | { 151 | public PBXReferenceProxy() : base() { 152 | } 153 | 154 | public PBXReferenceProxy( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 155 | } 156 | } 157 | 158 | public class PBXVariantGroup : PBXObject 159 | { 160 | public PBXVariantGroup() : base() { 161 | } 162 | 163 | public PBXVariantGroup( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) { 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /plugins/ios/plugins/XAlbumCameraController.mm: -------------------------------------------------------------------------------- 1 | 2 | #import "XAlbumCameraController.h" 3 | #import "XConfig.h" 4 | 5 | //转 6 | //http://blog.csdn.net/anyuanlzh/article/details/50748928 7 | 8 | @implementation XAlbumCameraController 9 | 10 | - (void)showPicker: 11 | (UIImagePickerControllerSourceType)type 12 | allowsEditing:(BOOL)flag 13 | { 14 | XLog(@" --- showPicker!!"); 15 | UIImagePickerController *picker = [[UIImagePickerController alloc] init]; 16 | picker.delegate = self; 17 | picker.sourceType = type; 18 | picker.allowsEditing = flag; 19 | 20 | [self presentViewController:picker animated:YES completion:nil]; 21 | } 22 | 23 | // 打开相册后选择照片时的响应方法 24 | - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info 25 | { 26 | XLog(@" --- imagePickerController didFinishPickingMediaWithInfo!!"); 27 | // Grab the image and write it to disk 28 | UIImage *image; 29 | UIImage *image2; 30 | // image = [info objectForKey:UIImagePickerControllerEditedImage]; 31 | image = [info objectForKey:UIImagePickerControllerOriginalImage]; 32 | 33 | UIGraphicsBeginImageContext(CGSizeMake(256,256)); 34 | [image drawInRect:CGRectMake(0, 0, 256, 256)]; 35 | image2 = UIGraphicsGetImageFromCurrentImageContext(); 36 | UIGraphicsEndImageContext(); 37 | 38 | // 得到了image,然后用你的函数传回u3d 39 | NSData *imgData; 40 | if(UIImagePNGRepresentation(image2) == nil) 41 | { 42 | XLog(@" --- actionSheet slse!! 11 "); 43 | imgData= UIImageJPEGRepresentation(image, .6); 44 | } 45 | else 46 | { 47 | XLog(@" --- actionSheet slse!! 22 "); 48 | imgData= UIImagePNGRepresentation(image2); 49 | } 50 | 51 | NSString *path = [self saveWithData:imgData]; 52 | XLog(@"path : %@", path); 53 | 54 | UnitySendMessage( "GameManager", "OnReceivePhoto", path.UTF8String); 55 | 56 | // 关闭相册 57 | [picker dismissViewControllerAnimated:YES completion:nil]; 58 | } 59 | 60 | // 打开相册后点击“取消”的响应 61 | - (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker 62 | { 63 | XLog(@" --- imagePickerControllerDidCancel !!"); 64 | [self dismissViewControllerAnimated:YES completion:nil]; 65 | } 66 | 67 | - (NSString *) saveWithData:(NSData *)data { 68 | 69 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES); 70 | NSString *homePath = [paths objectAtIndex:0]; 71 | NSString *filePath = [homePath stringByAppendingPathComponent:@"tmp.png"]; 72 | NSFileManager *fileManager = [NSFileManager defaultManager]; 73 | if ([fileManager fileExistsAtPath:filePath]) { 74 | XLog(@"FileExists"); 75 | } 76 | else { 77 | XLog(@"FileNotExists"); 78 | [fileManager createFileAtPath:filePath contents:nil attributes:nil]; 79 | } 80 | 81 | NSFileHandle* fhw=[NSFileHandle fileHandleForWritingAtPath:filePath]; 82 | [fhw truncateFileAtOffset:0]; 83 | 84 | [fhw writeData:data]; 85 | [fhw closeFile]; 86 | 87 | return filePath; 88 | } 89 | 90 | +(void) saveImageToPhotosAlbum:(NSString*) readAdr 91 | { 92 | XLog(@"readAdr: %@", readAdr); 93 | UIImage* image = [UIImage imageWithContentsOfFile:readAdr]; 94 | UIImageWriteToSavedPhotosAlbum(image, 95 | self, 96 | @selector(image:didFinishSavingWithError:contextInfo:), 97 | NULL); 98 | } 99 | 100 | +(void) image:(UIImage*)image didFinishSavingWithError:(NSError*)error contextInfo:(void*)contextInfo 101 | { 102 | NSString* result; 103 | if(error) 104 | { 105 | result = @"图片保存到相册失败!"; 106 | } 107 | else 108 | { 109 | result = @"图片保存到相册成功!"; 110 | } 111 | XLog(@"result : %@", result); 112 | // UnitySendMessage( "GameManager", "OnReceivePhoto", result.UTF8String); 113 | } 114 | @end 115 | 116 | extern "C" 117 | { 118 | void _TakePhoto() { 119 | if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) 120 | { 121 | XAlbumCameraController * app = [[XAlbumCameraController alloc] init]; 122 | UIViewController *vc = UnityGetGLViewController(); 123 | [vc.view addSubview: app.view]; 124 | 125 | [app showPicker:UIImagePickerControllerSourceTypePhotoLibrary allowsEditing:NO]; 126 | } 127 | else 128 | { 129 | UnitySendMessage( "GameManager", "OnReceivePhoto", ""); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | 2 | task :update do 3 | desc "update unity project and deploy project" 4 | system ("git pull && cd .. && git pull") 5 | 6 | desc "update gitversion in Assets/Resources/info.txt" 7 | # use printf , not use echo 8 | system ("cd .. && printf $(git rev-list HEAD --abbrev-commit --max-count=1) > Assets/Resources/info.txt") 9 | end 10 | 11 | namespace :compile do 12 | desc "compile unity ios" 13 | task :ios => :update do 14 | system ("ruby scripts/run_unity.rb -p ios") 15 | end 16 | desc "compile unity ios with compile log" 17 | task :ios_with_log => :update do 18 | system ("ruby scripts/run_unity.rb -p ios --is_log=true") 19 | end 20 | 21 | desc "compile unity android" 22 | task :android => :update do 23 | system("ruby scripts/run_unity.rb -p android") 24 | end 25 | 26 | desc "compile unity android with compile log" 27 | task :android_with_log => :update do 28 | system("ruby scripts/run_unity.rb -p android --is_log=true") 29 | end 30 | 31 | namespace :renchang do 32 | desc "compile renchang unity ios" 33 | task :ios => :update do 34 | system ("ruby scripts/run_unity_renchang.rb -p ios --is_log=true") 35 | 36 | # system ("ruby scripts/run_bearychat.rb 'IOS端编译完成' ") 37 | Rake::Task["bearychat:send"].invoke("Build-IOS-Over") 38 | Rake::Task["bearychat:send"].reenable 39 | end 40 | 41 | desc "compile renchang unity android" 42 | task :android => :update do 43 | system ("ruby scripts/run_unity_renchang.rb -p android --is_log=true") 44 | 45 | # system ("ruby scripts/run_bearychat.rb 'Android端编译完成' ") 46 | Rake::Task["bearychat:send"].invoke("Build-Android-Over") 47 | Rake::Task["bearychat:send"].reenable 48 | end 49 | end 50 | 51 | namespace :seabird do 52 | desc "build seabrid unity project" 53 | task :build => :update do |t, args| 54 | system ("ruby scripts/run_unity_seabird.rb -t #{ENV['target']} --is_log=true") 55 | end 56 | end 57 | end 58 | 59 | namespace :channel do 60 | desc "copy channel configs" 61 | task :copy do |t, args| 62 | system("ruby scripts/run_channel.rb #{ENV['channel']}") 63 | end 64 | end 65 | 66 | namespace :fir do 67 | desc "fir publish ios" 68 | task :ios, [:msg] do |t, args| 69 | args.with_defaults(:msg => '版本更新') 70 | system ("fir publish ./builds/ios.ipa -c " + args[:msg]) 71 | end 72 | 73 | desc "fir publish android" 74 | task :android, [:msg] do |t, args| 75 | args.with_defaults(:msg => '版本更新') 76 | system ("fir publish ./builds/android.apk -c " + args[:msg]) 77 | end 78 | end 79 | 80 | namespace :bearychat do 81 | desc "send msg to bearychat" 82 | task :send, [:msg] do |t, args| 83 | args.with_defaults(:msg => '空(nil)') 84 | system ("ruby scripts/run_bearychat.rb " + args[:msg]) 85 | end 86 | end 87 | 88 | namespace :bugly do 89 | desc "update dsym file to bugly" 90 | task :upload do 91 | system ("ruby scripts/run_bugly.rb") 92 | end 93 | end 94 | 95 | namespace :auto do 96 | desc "auto compile renchang-unity ios" 97 | task :ios do 98 | Rake::Task["compile:renchang:ios"].invoke 99 | Rake::Task["fir:ios"].invoke 100 | Rake::Task["bugly:upload"].invoke 101 | end 102 | 103 | desc "auto compile renchang-unity android" 104 | task :android do 105 | Rake::Task["compile:renchang:android"].invoke 106 | Rake::Task["fir:android"].invoke 107 | end 108 | 109 | desc "auto compile renchang-unity ios & android" 110 | task :all do 111 | Rake::Task["auto:ios"].invoke 112 | Rake::Task["auto:android"].invoke 113 | end 114 | end 115 | 116 | namespace :install do 117 | desc "install ios ipa" 118 | task :ios do 119 | system ("ios-deploy --bundle ./builds/ios.ipa") 120 | end 121 | 122 | desc "install android apk" 123 | task :android do 124 | system ("adb install -r ./builds/android.apk") 125 | end 126 | end 127 | 128 | # namespace :test do 129 | # task :test1 do 130 | # Rake::Task["bearychat:send"].invoke("t1") 131 | # Rake::Task["bearychat:send"].reenable 132 | # Rake::Task["bearychat:send"].invoke("t2") 133 | # end 134 | 135 | # task :test2 do |t, args| 136 | # puts t 137 | # puts args 138 | # end 139 | 140 | # task :test3 do 141 | # Rake::Task["test:test2"].execute("test", "12") 142 | # end 143 | # end -------------------------------------------------------------------------------- /plugins/ios/plugins/XGameCenter.mm: -------------------------------------------------------------------------------- 1 | // 2 | // XGameCenter.m 3 | // kp 4 | // 5 | // Created by DreamTim on 12/7/15. 6 | // Copyright © 2015 cmjstudio. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "XGameCenter.h" 11 | #import "XConfig.h" 12 | #import "UnityAppController.h" 13 | 14 | @implementation XGameCenter 15 | 16 | + (instancetype)shareInstance { 17 | static XGameCenter *_instance = nil; 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | _instance = [[XGameCenter alloc] init]; 21 | }); 22 | return _instance; 23 | } 24 | 25 | #pragma mark - GKGameCenterViewController代理方法 26 | -(void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController{ 27 | [gameCenterViewController dismissViewControllerAnimated:YES completion:nil]; 28 | } 29 | 30 | #pragma mark 私有方法 31 | - (void) openViewController:(UIViewController *)controller { 32 | UIViewController* viewController = GetAppController().rootViewController; 33 | [viewController presentViewController:controller animated:YES completion:nil]; 34 | } 35 | #pragma mark 自己的方法 36 | - (void)openGameCenter { 37 | if (![GKLocalPlayer localPlayer].isAuthenticated) { 38 | XLog(@"未获得用户授权."); 39 | NSString *title = NSLocalizedStringFromTable(@"Warn", @"InfoPlist", nil); 40 | NSString *message = NSLocalizedStringFromTable(@"GameCenterCannotOpen", @"InfoPlist", nil); 41 | NSString *ok = NSLocalizedStringFromTable(@"OK", @"InfoPlist", nil); 42 | 43 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:ok otherButtonTitles:nil]; 44 | [alertView show]; 45 | return; 46 | } 47 | //Game Center视图控制器 48 | GKGameCenterViewController *gameCenterController=[[GKGameCenterViewController alloc]init]; 49 | //设置代理 50 | gameCenterController.gameCenterDelegate=self; 51 | //显示 52 | [self openViewController:gameCenterController]; 53 | } 54 | 55 | //检查是否经过认证,如果没经过认证则弹出Game Center登录界面 56 | -(void)authorize { 57 | //创建一个本地用户 58 | GKLocalPlayer *localPlayer= [GKLocalPlayer localPlayer]; 59 | //检查用于授权,如果没有登录则让用户登录到GameCenter(注意此事件设置之后或点击登录界面的取消按钮都会被调用) 60 | [localPlayer setAuthenticateHandler:^(UIViewController * controller, NSError *error) { 61 | if ([[GKLocalPlayer localPlayer] isAuthenticated]) { 62 | XLog(@"已授权."); 63 | } else { 64 | /* 65 | //注意:在设置中找到Game Center,设置其允许沙盒,否则controller为nil 66 | if (controller != nil) { 67 | //登录 68 | [self openViewController:controller]; 69 | } 70 | else { 71 | XLog(@"在设置中找到Game Center,设置其允许沙盒"); 72 | } 73 | */ 74 | XLog(@"未授权"); 75 | } 76 | }]; 77 | } 78 | 79 | //设置percent成就完成度,100代表获得此成就 80 | -(void)addAchievementWithIdentifier:(NSString *)identifier percent:(double)value{ 81 | if (![GKLocalPlayer localPlayer].isAuthenticated) { 82 | XLog(@"未获得用户授权."); 83 | return; 84 | } 85 | 86 | //创建成就 87 | GKAchievement *achievement = [[GKAchievement alloc]initWithIdentifier:identifier]; 88 | achievement.percentComplete = value; 89 | 90 | //保存成就到Game Center服务器,注意保存是异步的,并且支持离线提交 91 | [GKAchievement reportAchievements:@[achievement] withCompletionHandler:^(NSError *error) { 92 | if(error){ 93 | XLog(@"保存成就过程中发生错误,错误信息:%@",error.localizedDescription); 94 | return ; 95 | } 96 | XLog(@"添加成就成功."); 97 | }]; 98 | } 99 | 100 | //排行榜 101 | -(void)addScoreWithIdentifier:(NSString *)identifier score:(int64_t)value{ 102 | if (![GKLocalPlayer localPlayer].isAuthenticated) { 103 | XLog(@"未获得用户授权."); 104 | return; 105 | } 106 | //创建积分对象 107 | GKScore *score = [[GKScore alloc]initWithLeaderboardIdentifier:identifier]; 108 | //设置得分 109 | score.value=value; 110 | //提交积分到Game Center服务器端,注意保存是异步的,并且支持离线提交 111 | [GKScore reportScores:@[score] withCompletionHandler:^(NSError *error) { 112 | if(error){ 113 | XLog(@"保存积分过程中发生错误,错误信息:%@",error.localizedDescription); 114 | return ; 115 | } 116 | XLog(@"添加积分成功."); 117 | }]; 118 | } 119 | 120 | extern "C" 121 | { 122 | //初始化 123 | void _InitGameCenter() 124 | { 125 | [[XGameCenter shareInstance] authorize]; 126 | } 127 | 128 | //打开游戏中心 129 | void _OpenGameCenter() 130 | { 131 | [[XGameCenter shareInstance] openGameCenter]; 132 | } 133 | 134 | //添加排行榜数据 135 | void _AddGameCenterScore(const char* id, int value) 136 | { 137 | [[XGameCenter shareInstance] addScoreWithIdentifier:PSTRING(id) score:value]; 138 | } 139 | 140 | //添加成就 141 | void _AddGameCenterAchievement(const char* id, double percent) 142 | { 143 | [[XGameCenter shareInstance] addAchievementWithIdentifier:PSTRING(id) percent:percent]; 144 | } 145 | } 146 | @end -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXFileReference.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UnityEditor.XCodeEditor 6 | { 7 | public class PBXFileReference : PBXObject 8 | { 9 | protected const string PATH_KEY = "path"; 10 | protected const string NAME_KEY = "name"; 11 | protected const string SOURCETREE_KEY = "sourceTree"; 12 | protected const string EXPLICIT_FILE_TYPE_KEY = "explicitFileType"; 13 | protected const string LASTKNOWN_FILE_TYPE_KEY = "lastKnownFileType"; 14 | protected const string ENCODING_KEY = "fileEncoding"; 15 | 16 | public string buildPhase; 17 | public readonly Dictionary trees = new Dictionary { 18 | { TreeEnum.ABSOLUTE, "" }, 19 | { TreeEnum.GROUP, "" }, 20 | { TreeEnum.BUILT_PRODUCTS_DIR, "BUILT_PRODUCTS_DIR" }, 21 | { TreeEnum.DEVELOPER_DIR, "DEVELOPER_DIR" }, 22 | { TreeEnum.SDKROOT, "SDKROOT" }, 23 | { TreeEnum.SOURCE_ROOT, "SOURCE_ROOT" } 24 | }; 25 | 26 | public static readonly Dictionary typeNames = new Dictionary { 27 | { ".a", "archive.ar" }, 28 | { ".app", "wrapper.application" }, 29 | { ".s", "sourcecode.asm" }, 30 | { ".c", "sourcecode.c.c" }, 31 | { ".cpp", "sourcecode.cpp.cpp" }, 32 | { ".framework", "wrapper.framework" }, 33 | { ".h", "sourcecode.c.h" }, 34 | { ".pch", "sourcecode.c.h" }, 35 | { ".icns", "image.icns" }, 36 | { ".m", "sourcecode.c.objc" }, 37 | { ".mm", "sourcecode.cpp.objcpp" }, 38 | { ".nib", "wrapper.nib" }, 39 | { ".plist", "text.plist.xml" }, 40 | { ".png", "image.png" }, 41 | { ".rtf", "text.rtf" }, 42 | { ".tiff", "image.tiff" }, 43 | { ".txt", "text" }, 44 | { ".xcodeproj", "wrapper.pb-project" }, 45 | { ".xib", "file.xib" }, 46 | { ".strings", "text.plist.strings" }, 47 | { ".bundle", "wrapper.plug-in" }, 48 | { ".dylib", "compiled.mach-o.dylib" } 49 | }; 50 | 51 | public static readonly Dictionary typePhases = new Dictionary { 52 | { ".a", "PBXFrameworksBuildPhase" }, 53 | { ".app", null }, 54 | { ".s", "PBXSourcesBuildPhase" }, 55 | { ".c", "PBXSourcesBuildPhase" }, 56 | { ".cpp", "PBXSourcesBuildPhase" }, 57 | { ".framework", "PBXFrameworksBuildPhase" }, 58 | { ".h", null }, 59 | { ".pch", null }, 60 | { ".icns", "PBXResourcesBuildPhase" }, 61 | { ".m", "PBXSourcesBuildPhase" }, 62 | { ".mm", "PBXSourcesBuildPhase" }, 63 | { ".nib", "PBXResourcesBuildPhase" }, 64 | { ".plist", "PBXResourcesBuildPhase" }, 65 | { ".png", "PBXResourcesBuildPhase" }, 66 | { ".rtf", "PBXResourcesBuildPhase" }, 67 | { ".tiff", "PBXResourcesBuildPhase" }, 68 | { ".txt", "PBXResourcesBuildPhase" }, 69 | { ".xcodeproj", null }, 70 | { ".xib", "PBXResourcesBuildPhase" }, 71 | { ".strings", "PBXResourcesBuildPhase" }, 72 | { ".bundle", "PBXResourcesBuildPhase" }, 73 | { ".dylib", "PBXFrameworksBuildPhase" } 74 | }; 75 | 76 | public PBXFileReference( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) 77 | { 78 | 79 | } 80 | 81 | //TODO see if XCode has a preference for ordering these attributes 82 | public PBXFileReference( string filePath, TreeEnum tree = TreeEnum.SOURCE_ROOT ) : base() 83 | { 84 | this.Add( PATH_KEY, filePath ); 85 | this.Add( NAME_KEY, System.IO.Path.GetFileName( filePath ) ); 86 | this.Add( SOURCETREE_KEY, (string)( System.IO.Path.IsPathRooted( filePath ) ? trees[TreeEnum.ABSOLUTE] : trees[tree] ) ); 87 | this.GuessFileType(); 88 | } 89 | 90 | public string name { 91 | get { 92 | if( !ContainsKey( NAME_KEY ) ) { 93 | return null; 94 | } 95 | return (string)_data[NAME_KEY]; 96 | } 97 | } 98 | 99 | public string path { 100 | get { 101 | if( !ContainsKey( PATH_KEY ) ) { 102 | return null; 103 | } 104 | return (string)_data[PATH_KEY]; 105 | } 106 | } 107 | 108 | private void GuessFileType() 109 | { 110 | this.Remove( EXPLICIT_FILE_TYPE_KEY ); 111 | this.Remove( LASTKNOWN_FILE_TYPE_KEY ); 112 | string extension = System.IO.Path.GetExtension( (string)_data[ PATH_KEY ] ); 113 | if( !PBXFileReference.typeNames.ContainsKey( extension ) ){ 114 | Debug.LogWarning( "Unknown file extension: " + extension + "\nPlease add extension and Xcode type to PBXFileReference.types" ); 115 | return; 116 | } 117 | 118 | this.Add( LASTKNOWN_FILE_TYPE_KEY, PBXFileReference.typeNames[ extension ] ); 119 | this.buildPhase = PBXFileReference.typePhases[ extension ]; 120 | } 121 | 122 | private void SetFileType( string fileType ) 123 | { 124 | this.Remove( EXPLICIT_FILE_TYPE_KEY ); 125 | this.Remove( LASTKNOWN_FILE_TYPE_KEY ); 126 | 127 | this.Add( EXPLICIT_FILE_TYPE_KEY, fileType ); 128 | } 129 | } 130 | 131 | public enum TreeEnum { 132 | ABSOLUTE, 133 | GROUP, 134 | BUILT_PRODUCTS_DIR, 135 | DEVELOPER_DIR, 136 | SDKROOT, 137 | SOURCE_ROOT 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | XUnityDeploy是针对Unity自动化编译Android/IOS的脚本。 2 | 3 | ## 环境 4 | 5 | * `Xcode` Version > 7.0 6 | 7 | * `Ruby` Version > 2.0 8 | 9 | * `Unity` Version > 5.0, 其中需要配置Android SDK,JDK, NDK等 10 | 11 | * `Git` or `Svn` 12 | 13 | * [`ios-deploy`][ios-deploy] 14 | 15 | ## 步骤 16 | 17 | * 把`XUnityDeploy`放在`Assets`的同级目录 18 | 19 | * 拷贝`plugins/Editor`到`Assets/Editor` 20 | 21 | * 配置参数`XUnityDeploy_configs`下的`main.info.json`, `main.projmods.json`, `export.plist`, `unity_deploy.plist` 22 | * `ruby scripts/run_unity.rb -p ios[android]` 23 | 24 | ## XUnityDeploy的流程图 25 | 26 | 1. 由`run_unity`启动脚本 27 | 2. `Ruby`脚本生成`Unity`需要的配置`unity_deploy` 28 | 3. `XUnityDeploy`读取`unity_deploy`配置,配置`Unity`项目,最后编译项目 29 | 4. `UnityDeployPostprocess`在编译`Unity`之后需要配置`Xcode`项目(IOS) 30 | 31 | * 读取`main.build`,获取`info` 32 | * 获取`projmods`,配置`Xcode`项目 33 | * 获取`info`, 配置`Xcode`的`Info.plist` 34 | 35 | 5. 对生成包进行重命名,并提交到down serve上 36 | 37 | ## Rake使用 38 | * rake update 更新项目 39 | * rake compile:ios 编译ios项目 40 | * rake compile:android 编译android项目 41 | * rake install:ios 安装ios包 42 | * rake install:android 安装android包 43 | ## 目录结构说明 44 | 45 | * builds 最终生成包的目录(apk, xcode project, ipa) 46 | 47 | * ../XUnityDeploy_configs 配置文件目录 48 | + `android.keystore` 是`Android`的签名文件,需要自己替换,并在`unity_deploy`中配置keystore 49 | + `export.plist` 是导出`ipa`包`xcode`需要的配置文件。其中`method`是`app-store`,`enterprise`, `ad-hoc`,`development`。参考[export.plist][export] 50 | 51 | ```text 52 | 53 | 54 | 55 | 56 | compileBitcode 57 | 58 | method 59 | development 60 | teamID 61 | your team id 62 | provisioningProfiles 63 | 64 | your bundle id 65 | your provision name 66 | 67 | signingCertificate 68 | iPhone Developer 69 | signingStyle 70 | manual 71 | 72 | 73 | ``` 74 | + `unity_deploy.json` 是`Unity`在`XUnityDeploy`中读取的配置,用于配置`Unity`的项目 75 | 76 | ```json 77 | { 78 | "ios" : 79 | { 80 | "Version" : "1.0", 81 | "BundleVersionCode": 1, 82 | "BundleIdentifier" : "com.501joy.over14", 83 | "ProductName" : "XUnityDeploy", 84 | "IsDevelopment" : false, 85 | "DefineSymbols" : "EXAMPLE", 86 | "StrippingLevel" : "UseMicroMSCorlib", 87 | "ScriptCallOptimizationLevel" : "FastButNoExceptions", 88 | "IOSTargetOSVersion" : "7.0", 89 | "AotOptions" : "nimt-trampolines=256", 90 | "ScriptBackent" : "IL2CPP", 91 | "Channel" : "500026" 92 | }, 93 | "android" : 94 | { 95 | "Version" : "1.0", 96 | "BundleVersionCode" : 1, 97 | "BundleIdentifier" : "com.501joy.over14", 98 | "ProductName" : "XUnityDeploy", 99 | "IsDevelopment" : false, 100 | "DefineSymbols" : "EXAMPLE", 101 | "StrippingLevel" : "UseMicroMSCorlib", 102 | "OBB" : false, 103 | "ScriptBackent" : "IL2CPP", 104 | "KeystoreName" : "android.keystore", 105 | "KeystorePass" : "example", 106 | "KeyaliasName" : "example", 107 | "KeyaliasPass" : "example", 108 | "Channel" : "600001" 109 | } 110 | } 111 | ``` 112 | 113 | * jenkins jenkins目录 114 | 115 | * logs 编译日志目录 116 | 117 | * scripts 运行脚本命令目录 118 | 119 | * tools 工具目录 120 | 121 | * unitys 编译脚本目录 122 | 123 | * utils 帮助脚本目录 124 | 125 | 126 | ## `说明` 127 | * `executeMethod class 'XUnityDeploy' could not be found` 需要把`plugins/Editor`拷贝到`Assets/Editor` 128 | 129 | * 这里关于`xcode`项目的配置在`Unity`的`XUnityDeploy`处理了,也就是配置`main.projmods.json`中的`build_settings`。这里还有一种方法在编译`Unity`之后,通过[Xcodeproj][url]配置`xcode` 130 | 131 | * `Error Domain=IDEDistributionErrorDomain Code=1 "The operation couldn’t be completed` 修改`export.plist`。还碰到过`WWDR certificate expired`, 需要重新更新一下,[参考][wwdr] 132 | 133 | * 因为`Unity`切换平台(ios/android)比较慢,所以这里建议针对ios/android单独checkout一个目录 134 | 135 | * `Error Domain=IDEDistributionErrorDomain Code=14 "No applicable devices found."`。这里需要把`Ruby`版本设置为`system`,即使用`rvm use system` [参考][error_code14] 136 | 137 | --- 138 | 139 | ### 关于对i18n支持 140 | 141 | 在项目中,需要支持游戏APP应用名称多国化,只需要配置一个`i18n.projmods.json`即可,在`i18n.projmods.json`中,指定对于的`InfoPlist.strings`, 比如 142 | 143 | ``` 144 | "files": 145 | [ 146 | "i18n/en.lproj/InfoPlist.strings", 147 | "i18n/zh-Hans.lproj/InfoPlist.strings", 148 | "i18n/zh-Hant.lproj/InfoPlist.strings" 149 | ], 150 | ``` 151 | 152 | __需要注意的是,在`XCProject.cs`中,需要ignore重复存在的情况__ 153 | 154 | ## TODO 155 | 156 | * 检查编译环境的脚本 157 | 158 | * 自动提交客户端的脚本 159 | 160 | * 用`Xcodeproj`替代`Editor/XCodeEditor` 161 | 162 | [url]: https://github.com/CocoaPods/Xcodeproj 163 | [export]: http://www.matrixprojects.net/p/xcodebuild-export-options-plist/ 164 | [error_code14]: https://stackoverflow.com/questions/33901132/export-failed-error-using-xcodebuild-command-line-tool 165 | [wwdr]: http://ajmccall.com/idedistributionerrordomain-code-1-error-and-fastlane/ 166 | [ios-deploy]: https://github.com/phonegap/ios-deploy 167 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/XCBuildConfiguration.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | namespace UnityEditor.XCodeEditor 5 | { 6 | public class XCBuildConfiguration : PBXObject 7 | { 8 | protected const string BUILDSETTINGS_KEY = "buildSettings"; 9 | protected const string HEADER_SEARCH_PATHS_KEY = "HEADER_SEARCH_PATHS"; 10 | protected const string LIBRARY_SEARCH_PATHS_KEY = "LIBRARY_SEARCH_PATHS"; 11 | protected const string FRAMEWORK_SEARCH_PATHS_KEY = "FRAMEWORK_SEARCH_PATHS"; 12 | protected const string OTHER_C_FLAGS_KEY = "OTHER_CFLAGS"; 13 | protected const string OTHER_LDFLAGS_KEY = "OTHER_LDFLAGS"; 14 | 15 | public XCBuildConfiguration( string guid, PBXDictionary dictionary ) : base( guid, dictionary ) 16 | { 17 | 18 | } 19 | 20 | public PBXSortedDictionary buildSettings { 21 | get { 22 | if( ContainsKey( BUILDSETTINGS_KEY ) ) { 23 | if (_data[BUILDSETTINGS_KEY].GetType() == typeof(PBXDictionary)) { 24 | PBXSortedDictionary ret = new PBXSortedDictionary(); 25 | ret.Append((PBXDictionary)_data[BUILDSETTINGS_KEY]); 26 | return ret; 27 | } 28 | return (PBXSortedDictionary)_data[BUILDSETTINGS_KEY]; 29 | } 30 | return null; 31 | } 32 | } 33 | 34 | protected bool AddSearchPaths( string path, string key, bool recursive = true ) 35 | { 36 | PBXList paths = new PBXList(); 37 | paths.Add( path ); 38 | return AddSearchPaths( paths, key, recursive ); 39 | } 40 | 41 | protected bool AddSearchPaths( PBXList paths, string key, bool recursive = true, bool quoted = false ) //we want no quoting whenever we can get away with it 42 | { 43 | //Debug.Log ("AddSearchPaths " + paths + key + (recursive?" recursive":"") + " " + (quoted?" quoted":"")); 44 | bool modified = false; 45 | 46 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 47 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 48 | 49 | foreach( string path in paths ) { 50 | string currentPath = path; 51 | //Debug.Log ("path " + currentPath); 52 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey( key ) ) { 53 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add( key, new PBXList() ); 54 | } 55 | else if( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[key] is string ) { 56 | PBXList list = new PBXList(); 57 | list.Add( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[key] ); 58 | ((PBXDictionary)_data[BUILDSETTINGS_KEY])[key] = list; 59 | } 60 | 61 | //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. 62 | if (currentPath.Contains(@" ")) quoted = true; 63 | 64 | if (quoted) { 65 | //if it ends in "/**", it wants to be recursive, and the "/**" needs to be _outside_ the quotes 66 | if (currentPath.EndsWith("/**")) { 67 | currentPath = "\\\"" + currentPath.Replace("/**", "\\\"/**"); 68 | } else { 69 | currentPath = "\\\"" + currentPath + "\\\""; 70 | } 71 | } 72 | //Debug.Log ("currentPath = " + currentPath); 73 | if( !((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[key]).Contains( currentPath ) ) { 74 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[key]).Add( currentPath ); 75 | modified = true; 76 | } 77 | } 78 | 79 | return modified; 80 | } 81 | 82 | public bool AddHeaderSearchPaths( PBXList paths, bool recursive = true ) 83 | { 84 | return this.AddSearchPaths( paths, HEADER_SEARCH_PATHS_KEY, recursive ); 85 | } 86 | 87 | public bool AddLibrarySearchPaths( PBXList paths, bool recursive = true ) 88 | { 89 | Debug.Log ("AddLibrarySearchPaths " + paths); 90 | return this.AddSearchPaths( paths, LIBRARY_SEARCH_PATHS_KEY, recursive ); 91 | } 92 | 93 | public bool AddFrameworkSearchPaths( PBXList paths, bool recursive = true ) 94 | { 95 | return this.AddSearchPaths( paths, FRAMEWORK_SEARCH_PATHS_KEY, recursive ); 96 | } 97 | 98 | public bool AddOtherCFlags( string flag ) 99 | { 100 | //Debug.Log( "INIZIO 1" ); 101 | PBXList flags = new PBXList(); 102 | flags.Add( flag ); 103 | return AddOtherCFlags( flags ); 104 | } 105 | 106 | public bool AddOtherCFlags( PBXList flags ) 107 | { 108 | //Debug.Log( "INIZIO 2" ); 109 | 110 | bool modified = false; 111 | 112 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 113 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 114 | 115 | foreach( string flag in flags ) { 116 | 117 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey( OTHER_C_FLAGS_KEY ) ) { 118 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add( OTHER_C_FLAGS_KEY, new PBXList() ); 119 | } 120 | else if ( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ OTHER_C_FLAGS_KEY ] is string ) { 121 | string tempString = (string)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_C_FLAGS_KEY]; 122 | ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ OTHER_C_FLAGS_KEY ] = new PBXList(); 123 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_C_FLAGS_KEY]).Add( tempString ); 124 | } 125 | 126 | if( !((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_C_FLAGS_KEY]).Contains( flag ) ) { 127 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_C_FLAGS_KEY]).Add( flag ); 128 | modified = true; 129 | } 130 | } 131 | 132 | return modified; 133 | } 134 | 135 | public bool AddOtherLinkerFlags( string flag ) 136 | { 137 | PBXList flags = new PBXList(); 138 | flags.Add( flag ); 139 | return AddOtherLinkerFlags( flags ); 140 | } 141 | 142 | public bool AddOtherLinkerFlags( PBXList flags ) 143 | { 144 | bool modified = false; 145 | 146 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) 147 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 148 | 149 | foreach( string flag in flags ) { 150 | 151 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey( OTHER_LDFLAGS_KEY ) ) { 152 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add( OTHER_LDFLAGS_KEY, new PBXList() ); 153 | } 154 | else if ( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ OTHER_LDFLAGS_KEY ] is string ) { 155 | string tempString = (string)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_LDFLAGS_KEY]; 156 | ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ OTHER_LDFLAGS_KEY ] = new PBXList(); 157 | if( !string.IsNullOrEmpty(tempString) ) { 158 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_LDFLAGS_KEY]).Add( tempString ); 159 | } 160 | } 161 | 162 | if( !((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_LDFLAGS_KEY]).Contains( flag ) ) { 163 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[OTHER_LDFLAGS_KEY]).Add( flag ); 164 | modified = true; 165 | } 166 | } 167 | 168 | return modified; 169 | } 170 | 171 | public bool overwriteBuildSetting(string settingName, string settingValue) { 172 | Debug.Log ("overwriteBuildSetting " + settingName + " " + settingValue); 173 | bool modified = false; 174 | 175 | if( !ContainsKey( BUILDSETTINGS_KEY ) ) { 176 | Debug.Log ("creating key " + BUILDSETTINGS_KEY); 177 | this.Add( BUILDSETTINGS_KEY, new PBXSortedDictionary() ); 178 | } 179 | 180 | if( !((PBXDictionary)_data[BUILDSETTINGS_KEY]).ContainsKey( settingName ) ) { 181 | Debug.Log("adding key " + settingName); 182 | ((PBXDictionary)_data[BUILDSETTINGS_KEY]).Add( settingName, new PBXList() ); 183 | } 184 | else if ( ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ settingName ] is string ) { 185 | //Debug.Log("key is string:" + settingName); 186 | //string tempString = (string)((PBXDictionary)_data[BUILDSETTINGS_KEY])[settingName]; 187 | ((PBXDictionary)_data[BUILDSETTINGS_KEY])[ settingName ] = new PBXList(); 188 | //((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[settingName]).Add( tempString ); 189 | } 190 | 191 | if( !((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[settingName]).Contains( settingValue ) ) { 192 | Debug.Log("setting " + settingName + " to " + settingValue); 193 | ((PBXList)((PBXDictionary)_data[BUILDSETTINGS_KEY])[settingName]).Add( settingValue ); 194 | modified = true; 195 | } 196 | 197 | return modified; 198 | } 199 | } 200 | } -------------------------------------------------------------------------------- /plugins/Editor/Deploy/XUnityDeploy.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System.Collections; 4 | using System.IO; 5 | using System.Collections.Generic; 6 | //using System.Threading; 7 | using System; 8 | 9 | public class XUnityDeploy 10 | { 11 | public static string[] Levels 12 | { 13 | get 14 | { 15 | string[] scenes = new string[EditorBuildSettings.scenes.Length]; 16 | for (int i = 0; i < EditorBuildSettings.scenes.Length; i++) 17 | { 18 | scenes[i] = EditorBuildSettings.scenes[i].path; 19 | } 20 | return scenes; 21 | } 22 | } 23 | 24 | //is Development mode 25 | static bool IsDevelopment 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | static string UnityDeployConfigPath = "XUnityDeploy/configs/"; 32 | 33 | /// 34 | /// get build path name of target 35 | /// 36 | /// The path name. 37 | /// Target. 38 | static string BuildPathName(BuildTargetGroup target) 39 | { 40 | var path = ""; 41 | string unityDeployBuildPath = "XUnityDeploy/builds"; 42 | if (target == BuildTargetGroup.iOS) 43 | { 44 | path = System.IO.Path.Combine(Application.dataPath.Replace("Assets", ""), unityDeployBuildPath + "/ios"); 45 | } 46 | else if (target == BuildTargetGroup.Android) 47 | { 48 | path = string.Format("./{0}/android.apk", unityDeployBuildPath); 49 | } 50 | return path; 51 | } 52 | 53 | //load deploy unity config 54 | static Hashtable LoadConfig(BuildTargetGroup target) 55 | { 56 | string unityCfg = UnityDeployConfigPath + "unity_deploy.json"; 57 | 58 | var path = System.IO.Path.Combine(Application.dataPath.Replace("Assets", ""), unityCfg); 59 | 60 | var content = File.ReadAllText(path); 61 | var data = (Hashtable)XUPorterJSON.MiniJSON.jsonDecode(content); 62 | 63 | if (data == null) 64 | { 65 | throw new Exception("Read deploy unity error"); 66 | } 67 | 68 | if (target == BuildTargetGroup.iOS) 69 | { 70 | return data["ios"] as Hashtable; 71 | } 72 | else 73 | { 74 | return data["android"] as Hashtable; 75 | } 76 | } 77 | 78 | //set unity version , symbols and others config 79 | static void UpdateUnityConfig(BuildTargetGroup target) 80 | { 81 | Hashtable config = LoadConfig(target); 82 | if (config != null) 83 | { 84 | var version = "Version"; 85 | if (config.ContainsKey(version)) { 86 | PlayerSettings.bundleVersion = config[version].ToString(); 87 | } 88 | 89 | var bundleId = "BundleIdentifier"; 90 | if (config.ContainsKey(bundleId)) 91 | { 92 | PlayerSettings.bundleIdentifier = config[bundleId].ToString(); 93 | } 94 | 95 | //product name 96 | var productName = "ProductName"; 97 | if (config.ContainsKey(productName)) 98 | { 99 | PlayerSettings.productName = config[productName].ToString(); 100 | } 101 | 102 | var isDevelop = "IsDevelopment"; 103 | if (config.ContainsKey(isDevelop)) 104 | { 105 | IsDevelopment = (bool)config[isDevelop]; 106 | } 107 | 108 | var definesymbols = "DefineSymbols"; 109 | if (config.ContainsKey(definesymbols)) 110 | { 111 | PlayerSettings.SetScriptingDefineSymbolsForGroup(target, config[definesymbols].ToString()); 112 | } 113 | 114 | //修改symlinklibraries 内容 115 | //现在IOS可以采用useMicroMSCorlib 这个属性了,之前听说protobuf不可以 116 | var strippingLevel = "StrippingLevel"; 117 | if (config.ContainsKey(strippingLevel)) { 118 | // StrippingLevel.UseMicroMSCorlib; 119 | PlayerSettings.strippingLevel = (StrippingLevel)Enum.Parse(typeof(StrippingLevel), config[strippingLevel].ToString(), true); 120 | } 121 | 122 | //android 123 | if (target == BuildTargetGroup.Android) 124 | { 125 | //obb 126 | var obb = "OBB"; 127 | if (config.ContainsKey(obb)) 128 | { 129 | PlayerSettings.Android.useAPKExpansionFiles = (bool)config[obb]; 130 | } 131 | else 132 | { 133 | PlayerSettings.Android.useAPKExpansionFiles = false; 134 | } 135 | 136 | //key store 137 | { 138 | var keystoreName = "KeystoreName"; 139 | var keystorePass = "KeystorePass"; 140 | var keyaliasName = "KeyaliasName"; 141 | var keyaliasPass = "KeyaliasPass"; 142 | 143 | if (config.ContainsKey(keystoreName)) 144 | { 145 | //"XUnityDelopy/config/Android_KeyStore/xx.keystore"; 146 | PlayerSettings.Android.keystoreName = UnityDeployConfigPath + config[keystoreName].ToString(); 147 | } 148 | if (config.ContainsKey(keystorePass)) 149 | { 150 | PlayerSettings.Android.keystorePass = config[keystorePass].ToString(); 151 | } 152 | if (config.ContainsKey(keyaliasName)) 153 | { 154 | PlayerSettings.Android.keyaliasName = config[keyaliasName].ToString(); 155 | } 156 | if (config.ContainsKey(keyaliasPass)) 157 | { 158 | PlayerSettings.keyaliasPass = config[keyaliasPass].ToString(); 159 | } 160 | } 161 | 162 | //android versionCode 163 | var bundleVersionCode = "BundleVersionCode"; 164 | if (config.ContainsKey(bundleVersionCode)) 165 | { 166 | PlayerSettings.Android.bundleVersionCode = System.Convert.ToInt32(config[bundleVersionCode]); 167 | } 168 | Debug.Log("**** versionCode : " + PlayerSettings.Android.bundleVersionCode.ToString()); 169 | } 170 | 171 | //ios 172 | if (target == BuildTargetGroup.iOS) 173 | { 174 | //采用fast no exception 175 | var scriptCallOptimization = "ScriptCallOptimizationLevel"; 176 | if (config.ContainsKey(scriptCallOptimization)) { 177 | // ScriptCallOptimizationLevel.FastButNoExceptions; 178 | PlayerSettings.iOS.scriptCallOptimization = (ScriptCallOptimizationLevel)Enum.Parse(typeof(ScriptCallOptimizationLevel), config[scriptCallOptimization].ToString(), true); 179 | } 180 | 181 | //设置ios最低版本为ios7.0 182 | var targetOSVersion = "IOSTargetOSVersion"; 183 | if (config.ContainsKey(targetOSVersion)) { 184 | // iOSTargetOSVersion.iOS_7_0; 185 | PlayerSettings.iOS.targetOSVersion = (iOSTargetOSVersion)Enum.Parse(typeof(iOSTargetOSVersion), config[targetOSVersion].ToString(), true); 186 | } 187 | 188 | //修改AOT的参数,否则会出现Ran Out Of Trampolines of Type 2 189 | var aotOptions = "AotOptions"; 190 | if (config.ContainsKey(aotOptions)) { 191 | // "nimt-trampolines=256"; 192 | PlayerSettings.aotOptions = config[aotOptions].ToString(); 193 | } 194 | } 195 | } 196 | else 197 | { 198 | throw new Exception("Unity Deploy Config is Null"); 199 | } 200 | } 201 | 202 | public static void BuildIOS() 203 | { 204 | UpdateUnityConfig(BuildTargetGroup.iOS); 205 | 206 | Debug.Log("**** Begin Deply IOS"); 207 | 208 | if (!IsDevelopment) 209 | { 210 | BuildPipeline.BuildPlayer(Levels, 211 | BuildPathName(BuildTargetGroup.iOS), 212 | BuildTarget.iOS, 213 | BuildOptions.SymlinkLibraries); 214 | } 215 | else 216 | { 217 | BuildPipeline.BuildPlayer(Levels, 218 | BuildPathName(BuildTargetGroup.iOS), 219 | BuildTarget.iOS, 220 | BuildOptions.SymlinkLibraries | BuildOptions.Development | BuildOptions.ConnectWithProfiler); 221 | } 222 | } 223 | 224 | public static void BuildAndroid() 225 | { 226 | UpdateUnityConfig(BuildTargetGroup.Android); 227 | 228 | Debug.Log("**** Begin Deply Android"); 229 | 230 | if (!IsDevelopment) 231 | { 232 | BuildPipeline.BuildPlayer(Levels, 233 | BuildPathName(BuildTargetGroup.Android), 234 | BuildTarget.Android, 235 | BuildOptions.None); 236 | } 237 | else 238 | { 239 | BuildPipeline.BuildPlayer(Levels, 240 | BuildPathName(BuildTargetGroup.Android), 241 | BuildTarget.Android, 242 | BuildOptions.Development); 243 | } 244 | } 245 | } -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/PBX Editor/PBXParser.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | 8 | 9 | namespace UnityEditor.XCodeEditor 10 | { 11 | public class PBXResolver 12 | { 13 | private class PBXResolverReverseIndex : Dictionary {} 14 | 15 | private PBXDictionary objects; 16 | private string rootObject; 17 | private PBXResolverReverseIndex index; 18 | 19 | public PBXResolver( PBXDictionary pbxData ) { 20 | this.objects = (PBXDictionary)pbxData[ "objects" ]; 21 | this.index = new PBXResolverReverseIndex(); 22 | this.rootObject = (string)pbxData[ "rootObject" ]; 23 | BuildReverseIndex(); 24 | } 25 | 26 | private void BuildReverseIndex() 27 | { 28 | foreach( KeyValuePair pair in this.objects ) 29 | { 30 | if( pair.Value is PBXBuildPhase ) 31 | { 32 | foreach( string guid in ((PBXBuildPhase)pair.Value).files ) 33 | { 34 | index[ guid ] = pair.Key; 35 | } 36 | } 37 | else if( pair.Value is PBXGroup ) 38 | { 39 | foreach( string guid in ((PBXGroup)pair.Value).children ) 40 | { 41 | index[ guid ] = pair.Key; 42 | } 43 | } 44 | } 45 | } 46 | 47 | public string ResolveName( string guid ) 48 | { 49 | 50 | if (!this.objects.ContainsKey(guid)) { 51 | Debug.LogWarning(this + " ResolveName could not resolve " + guid); 52 | return string.Empty; //"UNRESOLVED GUID:" + guid; 53 | } 54 | 55 | object entity = this.objects[ guid ]; 56 | 57 | if( entity is PBXBuildFile ) 58 | { 59 | return ResolveName( ((PBXBuildFile)entity).fileRef ); 60 | } 61 | else if( entity is PBXFileReference ) 62 | { 63 | PBXFileReference casted = (PBXFileReference)entity; 64 | return casted.name != null ? casted.name : casted.path; 65 | } 66 | else if( entity is PBXGroup ) 67 | { 68 | PBXGroup casted = (PBXGroup)entity; 69 | return casted.name != null ? casted.name : casted.path; 70 | } 71 | else if( entity is PBXProject || guid == this.rootObject ) 72 | { 73 | return "Project object"; 74 | } 75 | else if( entity is PBXFrameworksBuildPhase ) 76 | { 77 | return "Frameworks"; 78 | } 79 | else if( entity is PBXResourcesBuildPhase ) 80 | { 81 | return "Resources"; 82 | } 83 | else if( entity is PBXShellScriptBuildPhase ) 84 | { 85 | return "ShellScript"; 86 | } 87 | else if( entity is PBXSourcesBuildPhase ) 88 | { 89 | return "Sources"; 90 | } 91 | else if( entity is PBXCopyFilesBuildPhase ) 92 | { 93 | return "CopyFiles"; 94 | } 95 | else if( entity is XCConfigurationList ) 96 | { 97 | XCConfigurationList casted = (XCConfigurationList)entity; 98 | //Debug.LogWarning ("XCConfigurationList " + guid + " " + casted.ToString()); 99 | 100 | if( casted.data.ContainsKey( "defaultConfigurationName" ) ) { 101 | //Debug.Log ("XCConfigurationList " + (string)casted.data[ "defaultConfigurationName" ] + " " + guid); 102 | return (string)casted.data[ "defaultConfigurationName" ]; 103 | } 104 | 105 | return null; 106 | } 107 | else if( entity is PBXNativeTarget ) 108 | { 109 | PBXNativeTarget obj = (PBXNativeTarget)entity; 110 | //Debug.LogWarning ("PBXNativeTarget " + guid + " " + obj.ToString()); 111 | 112 | if( obj.data.ContainsKey( "name" ) ) { 113 | //Debug.Log ("PBXNativeTarget " + (string)obj.data[ "name" ] + " " + guid); 114 | return (string)obj.data[ "name" ]; 115 | } 116 | 117 | return null; 118 | } 119 | else if( entity is XCBuildConfiguration ) 120 | { 121 | XCBuildConfiguration obj = (XCBuildConfiguration)entity; 122 | //Debug.LogWarning ("XCBuildConfiguration UNRESOLVED GUID:" + guid + " " + (obj==null?"":obj.ToString())); 123 | 124 | if( obj.data.ContainsKey( "name" ) ) { 125 | //Debug.Log ("XCBuildConfiguration " + (string)obj.data[ "name" ] + " " + guid + " " + (obj==null?"":obj.ToString())); 126 | return (string)obj.data[ "name" ]; 127 | } 128 | 129 | } 130 | else if( entity is PBXObject ) 131 | { 132 | PBXObject obj = (PBXObject)entity; 133 | 134 | if( obj.data.ContainsKey( "name" ) ) 135 | Debug.Log ("PBXObject " + (string)obj.data[ "name" ] + " " + guid + " " + (obj==null?"":obj.ToString())); 136 | return (string)obj.data[ "name" ]; 137 | } 138 | 139 | //return "UNRESOLVED GUID:" + guid; 140 | Debug.LogWarning ("UNRESOLVED GUID:" + guid); 141 | return null; 142 | } 143 | 144 | public string ResolveBuildPhaseNameForFile( string guid ) 145 | { 146 | if( this.objects.ContainsKey( guid ) ) 147 | { 148 | object obj = this.objects[ guid ]; 149 | 150 | if( obj is PBXObject ) 151 | { 152 | PBXObject entity = (PBXObject)obj; 153 | 154 | if( this.index.ContainsKey( entity.guid ) ) 155 | { 156 | string parent_guid = this.index[ entity.guid ]; 157 | 158 | if( this.objects.ContainsKey( parent_guid ) ) 159 | { 160 | object parent = this.objects[ parent_guid ]; 161 | 162 | if( parent is PBXBuildPhase ) { 163 | string ret = ResolveName( ((PBXBuildPhase)parent).guid ); 164 | //Debug.Log ("ResolveBuildPhaseNameForFile = " + ret); 165 | return ret; 166 | } 167 | } 168 | } 169 | } 170 | } 171 | 172 | return null; 173 | } 174 | 175 | } 176 | 177 | public class PBXParser 178 | { 179 | public const string PBX_HEADER_TOKEN = "// !$*UTF8*$!\n"; 180 | public const char WHITESPACE_SPACE = ' '; 181 | public const char WHITESPACE_TAB = '\t'; 182 | public const char WHITESPACE_NEWLINE = '\n'; 183 | public const char WHITESPACE_CARRIAGE_RETURN = '\r'; 184 | public const char ARRAY_BEGIN_TOKEN = '('; 185 | public const char ARRAY_END_TOKEN = ')'; 186 | public const char ARRAY_ITEM_DELIMITER_TOKEN = ','; 187 | public const char DICTIONARY_BEGIN_TOKEN = '{'; 188 | public const char DICTIONARY_END_TOKEN = '}'; 189 | public const char DICTIONARY_ASSIGN_TOKEN = '='; 190 | public const char DICTIONARY_ITEM_DELIMITER_TOKEN = ';'; 191 | public const char QUOTEDSTRING_BEGIN_TOKEN = '"'; 192 | public const char QUOTEDSTRING_END_TOKEN = '"'; 193 | public const char QUOTEDSTRING_ESCAPE_TOKEN = '\\'; 194 | public const char END_OF_FILE = (char)0x1A; 195 | public const string COMMENT_BEGIN_TOKEN = "/*"; 196 | public const string COMMENT_END_TOKEN = "*/"; 197 | public const string COMMENT_LINE_TOKEN = "//"; 198 | private const int BUILDER_CAPACITY = 20000; 199 | 200 | private char[] data; 201 | private int index; 202 | private PBXResolver resolver; 203 | 204 | public PBXDictionary Decode( string data ) 205 | { 206 | if( !data.StartsWith( PBX_HEADER_TOKEN ) ) { 207 | Debug.Log( "Wrong file format." ); 208 | return null; 209 | } 210 | 211 | data = data.Substring( 13 ); 212 | this.data = data.ToCharArray(); 213 | index = 0; 214 | 215 | return (PBXDictionary)ParseValue(); 216 | } 217 | 218 | public string Encode( PBXDictionary pbxData, bool readable = false ) 219 | { 220 | this.resolver = new PBXResolver( pbxData ); 221 | StringBuilder builder = new StringBuilder( PBX_HEADER_TOKEN, BUILDER_CAPACITY ); 222 | 223 | bool success = SerializeValue( pbxData, builder, readable ); 224 | this.resolver = null; 225 | 226 | // Xcode adds newline at the end of file 227 | builder.Append( "\n" ); 228 | 229 | return ( success ? builder.ToString() : null ); 230 | } 231 | 232 | #region Pretty Print 233 | 234 | private void Indent( StringBuilder builder, int deep ) 235 | { 236 | builder.Append( "".PadLeft( deep, '\t' ) ); 237 | } 238 | 239 | private void Endline( StringBuilder builder, bool useSpace = false ) 240 | { 241 | builder.Append( useSpace ? " " : "\n" ); 242 | } 243 | 244 | private string marker = null; 245 | private void MarkSection( StringBuilder builder, string name ) 246 | { 247 | if( marker == null && name == null ) return; 248 | 249 | if( marker != null && name != marker ) 250 | { 251 | builder.Append( String.Format( "/* End {0} section */\n", marker ) ); 252 | } 253 | 254 | if( name != null && name != marker ) 255 | { 256 | builder.Append( String.Format( "\n/* Begin {0} section */\n", name ) ); 257 | } 258 | 259 | marker = name; 260 | } 261 | 262 | private bool GUIDComment( string guid, StringBuilder builder ) 263 | { 264 | string filename = this.resolver.ResolveName( guid ); 265 | string location = this.resolver.ResolveBuildPhaseNameForFile( guid ); 266 | 267 | //Debug.Log( "RESOLVE " + guid + ": " + filename + " in " + location ); 268 | 269 | if( filename != null ) { 270 | if( location != null ) { 271 | //Debug.Log( "GUIDComment " + guid + " " + String.Format( " /* {0} in {1} */", filename, location ) ); 272 | builder.Append( String.Format( " /* {0} in {1} */", filename, location ) ); 273 | } else { 274 | //Debug.Log( "GUIDComment " + guid + " " + String.Format( " /* {0} */", filename) ); 275 | builder.Append( String.Format( " /* {0} */", filename) ); 276 | } 277 | return true; 278 | } else { 279 | //string other = this.resolver.ResolveConfigurationNameForFile( guid ); 280 | Debug.Log ("GUIDComment " + guid + " [no filename]"); 281 | } 282 | 283 | return false; 284 | } 285 | 286 | #endregion 287 | 288 | #region Move 289 | 290 | private char NextToken() 291 | { 292 | SkipWhitespaces(); 293 | return StepForeward(); 294 | } 295 | 296 | private string Peek( int step = 1 ) 297 | { 298 | string sneak = string.Empty; 299 | for( int i = 1; i <= step; i++ ) { 300 | if( data.Length - 1 < index + i ) { 301 | break; 302 | } 303 | sneak += data[ index + i ]; 304 | } 305 | return sneak; 306 | } 307 | 308 | private bool SkipWhitespaces() 309 | { 310 | bool whitespace = false; 311 | while( Regex.IsMatch( StepForeward().ToString(), @"\s" ) ) 312 | whitespace = true; 313 | 314 | StepBackward(); 315 | 316 | if( SkipComments() ) { 317 | whitespace = true; 318 | SkipWhitespaces(); 319 | } 320 | 321 | return whitespace; 322 | } 323 | 324 | private bool SkipComments() 325 | { 326 | string s = string.Empty; 327 | string tag = Peek( 2 ); 328 | switch( tag ) { 329 | case COMMENT_BEGIN_TOKEN: { 330 | while( Peek( 2 ).CompareTo( COMMENT_END_TOKEN ) != 0 ) { 331 | s += StepForeward(); 332 | } 333 | s += StepForeward( 2 ); 334 | break; 335 | } 336 | case COMMENT_LINE_TOKEN: { 337 | while( !Regex.IsMatch( StepForeward().ToString(), @"\n" ) ) 338 | continue; 339 | 340 | break; 341 | } 342 | default: 343 | return false; 344 | } 345 | return true; 346 | } 347 | 348 | private char StepForeward( int step = 1 ) 349 | { 350 | index = Math.Min( data.Length, index + step ); 351 | return data[ index ]; 352 | } 353 | 354 | private char StepBackward( int step = 1 ) 355 | { 356 | index = Math.Max( 0, index - step ); 357 | return data[ index ]; 358 | } 359 | 360 | #endregion 361 | #region Parse 362 | 363 | private object ParseValue() 364 | { 365 | switch( NextToken() ) { 366 | case END_OF_FILE: 367 | Debug.Log( "End of file" ); 368 | return null; 369 | case DICTIONARY_BEGIN_TOKEN: 370 | return ParseDictionary(); 371 | case ARRAY_BEGIN_TOKEN: 372 | return ParseArray(); 373 | case QUOTEDSTRING_BEGIN_TOKEN: 374 | return ParseString(); 375 | default: 376 | StepBackward(); 377 | return ParseEntity(); 378 | } 379 | } 380 | 381 | private PBXDictionary ParseDictionary() 382 | { 383 | SkipWhitespaces(); 384 | PBXDictionary dictionary = new PBXDictionary(); 385 | string keyString = string.Empty; 386 | object valueObject = null; 387 | 388 | bool complete = false; 389 | while( !complete ) { 390 | switch( NextToken() ) { 391 | case END_OF_FILE: 392 | Debug.Log( "Error: reached end of file inside a dictionary: " + index ); 393 | complete = true; 394 | break; 395 | 396 | case DICTIONARY_ITEM_DELIMITER_TOKEN: 397 | keyString = string.Empty; 398 | valueObject = null; 399 | break; 400 | 401 | case DICTIONARY_END_TOKEN: 402 | keyString = string.Empty; 403 | valueObject = null; 404 | complete = true; 405 | break; 406 | 407 | case DICTIONARY_ASSIGN_TOKEN: 408 | valueObject = ParseValue(); 409 | if (!dictionary.ContainsKey(keyString)) { 410 | dictionary.Add( keyString, valueObject ); 411 | } 412 | break; 413 | 414 | default: 415 | StepBackward(); 416 | keyString = ParseValue() as string; 417 | break; 418 | } 419 | } 420 | return dictionary; 421 | } 422 | 423 | private PBXList ParseArray() 424 | { 425 | PBXList list = new PBXList(); 426 | bool complete = false; 427 | while( !complete ) { 428 | switch( NextToken() ) { 429 | case END_OF_FILE: 430 | Debug.Log( "Error: Reached end of file inside a list: " + list ); 431 | complete = true; 432 | break; 433 | case ARRAY_END_TOKEN: 434 | complete = true; 435 | break; 436 | case ARRAY_ITEM_DELIMITER_TOKEN: 437 | break; 438 | default: 439 | StepBackward(); 440 | list.Add( ParseValue() ); 441 | break; 442 | } 443 | } 444 | return list; 445 | } 446 | 447 | private object ParseString() 448 | { 449 | string s = string.Empty; 450 | char c = StepForeward(); 451 | while( c != QUOTEDSTRING_END_TOKEN ) { 452 | s += c; 453 | 454 | if( c == QUOTEDSTRING_ESCAPE_TOKEN ) 455 | s += StepForeward(); 456 | 457 | c = StepForeward(); 458 | } 459 | 460 | return s; 461 | } 462 | 463 | private object ParseEntity() 464 | { 465 | string word = string.Empty; 466 | 467 | while( !Regex.IsMatch( Peek(), @"[;,\s=]" ) ) { 468 | word += StepForeward(); 469 | } 470 | 471 | if( word.Length != 24 && Regex.IsMatch( word, @"^\d+$" ) ) { 472 | return Int32.Parse( word ); 473 | } 474 | 475 | return word; 476 | } 477 | 478 | #endregion 479 | #region Serialize 480 | 481 | private bool SerializeValue( object value, StringBuilder builder, bool readable = false, int indent = 0 ) 482 | { 483 | if( value == null ) { 484 | builder.Append( "null" ); 485 | } 486 | else if( value is PBXObject ) { 487 | SerializeDictionary( ((PBXObject)value).data, builder, readable, indent ); 488 | } 489 | else if( value is Dictionary ) { 490 | SerializeDictionary( (Dictionary)value, builder, readable, indent ); 491 | } 492 | else if( value.GetType().IsArray ) { 493 | SerializeArray( new ArrayList( (ICollection)value ), builder, readable, indent ); 494 | } 495 | else if( value is ArrayList ) { 496 | SerializeArray( (ArrayList)value, builder, readable, indent ); 497 | } 498 | else if( value is string ) { 499 | SerializeString( (string)value, builder, false, readable ); 500 | } 501 | else if( value is Char ) { 502 | SerializeString( Convert.ToString( (char)value ), builder, false, readable ); 503 | } 504 | else if( value is bool ) { 505 | builder.Append( Convert.ToInt32( value ).ToString() ); 506 | } 507 | else if( value.GetType().IsPrimitive ) { 508 | builder.Append( Convert.ToString( value ) ); 509 | } 510 | else { 511 | Debug.LogWarning( "Error: unknown object of type " + value.GetType().Name ); 512 | return false; 513 | } 514 | 515 | return true; 516 | } 517 | 518 | private bool SerializeDictionary( Dictionary dictionary, StringBuilder builder, bool readable = false, int indent = 0 ) 519 | { 520 | builder.Append( DICTIONARY_BEGIN_TOKEN ); 521 | if( readable ) Endline( builder ); 522 | 523 | foreach( KeyValuePair pair in dictionary ) 524 | { 525 | // output section banner if necessary 526 | if( readable && indent == 1 ) MarkSection( builder, pair.Value.GetType().Name ); 527 | 528 | // indent KEY 529 | if( readable ) Indent( builder, indent + 1 ); 530 | 531 | // KEY 532 | SerializeString( pair.Key, builder, false, readable ); 533 | 534 | // = 535 | // FIX ME: cannot resolve mode because readable = false for PBXBuildFile/Reference sections 536 | builder.Append( String.Format( " {0} ", DICTIONARY_ASSIGN_TOKEN ) ); 537 | 538 | // VALUE 539 | // do not pretty-print PBXBuildFile or PBXFileReference as Xcode does 540 | //Debug.Log ("about to serialize " + pair.Value.GetType () + " " + pair.Value); 541 | SerializeValue( pair.Value, builder, ( readable && 542 | ( pair.Value.GetType() != typeof( PBXBuildFile ) ) && 543 | ( pair.Value.GetType() != typeof( PBXFileReference ) ) 544 | ), indent + 1 ); 545 | 546 | // end statement 547 | builder.Append( DICTIONARY_ITEM_DELIMITER_TOKEN ); 548 | 549 | // FIX ME: negative readable in favor of nice output for PBXBuildFile/Reference sections 550 | Endline( builder, !readable ); 551 | } 552 | 553 | // output last section banner 554 | if( readable && indent == 1 ) MarkSection( builder, null ); 555 | 556 | // indent } 557 | if( readable ) Indent( builder, indent ); 558 | 559 | builder.Append( DICTIONARY_END_TOKEN ); 560 | 561 | return true; 562 | } 563 | 564 | private bool SerializeArray( ArrayList anArray, StringBuilder builder, bool readable = false, int indent = 0 ) 565 | { 566 | builder.Append( ARRAY_BEGIN_TOKEN ); 567 | if( readable ) Endline( builder ); 568 | 569 | for( int i = 0; i < anArray.Count; i++ ) 570 | { 571 | object value = anArray[i]; 572 | 573 | if( readable ) Indent( builder, indent + 1 ); 574 | 575 | if( !SerializeValue( value, builder, readable, indent + 1 ) ) 576 | { 577 | return false; 578 | } 579 | 580 | builder.Append( ARRAY_ITEM_DELIMITER_TOKEN ); 581 | 582 | // FIX ME: negative readable in favor of nice output for PBXBuildFile/Reference sections 583 | Endline( builder, !readable ); 584 | } 585 | 586 | if( readable ) Indent( builder, indent ); 587 | builder.Append( ARRAY_END_TOKEN ); 588 | 589 | return true; 590 | } 591 | 592 | private bool SerializeString( string aString, StringBuilder builder, bool useQuotes = false, bool readable = false ) 593 | { 594 | //Debug.Log ("SerializeString " + aString); 595 | // Is a GUID? 596 | // Note: Unity3d generates mixed-case GUIDs, Xcode use uppercase GUIDs only. 597 | if( Regex.IsMatch( aString, @"^[A-Fa-f0-9]{24}$" ) ) { 598 | builder.Append( aString ); 599 | GUIDComment( aString, builder ); 600 | return true; 601 | } 602 | 603 | // Is an empty string? 604 | if( string.IsNullOrEmpty( aString ) ) { 605 | builder.Append( QUOTEDSTRING_BEGIN_TOKEN ); 606 | builder.Append( QUOTEDSTRING_END_TOKEN ); 607 | return true; 608 | } 609 | 610 | // FIX ME: Original regexp was: @"^[A-Za-z0-9_.]+$", we use modified regexp with '/-' allowed 611 | // to workaround Unity bug when all PNGs had "Libraries/" (group name) added to their paths after append 612 | if( !Regex.IsMatch( aString, @"^[A-Za-z0-9_./-]+$" ) ) { 613 | useQuotes = true; 614 | } 615 | 616 | if( useQuotes ) 617 | builder.Append( QUOTEDSTRING_BEGIN_TOKEN ); 618 | 619 | builder.Append( aString ); 620 | 621 | if( useQuotes ) 622 | builder.Append( QUOTEDSTRING_END_TOKEN ); 623 | 624 | return true; 625 | } 626 | 627 | #endregion 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /plugins/Editor/XCodeEditor/MiniJSON/MiniJSON.cs: -------------------------------------------------------------------------------- 1 | namespace XUPorterJSON { 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Text; 6 | using System.Collections.Generic; 7 | 8 | 9 | /* Based on the JSON parser from 10 | * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html 11 | * 12 | * I simplified it so that it doesn't throw exceptions 13 | * and can be used in Unity iPhone with maximum code stripping. 14 | */ 15 | /// 16 | /// This class encodes and decodes JSON strings. 17 | /// Spec. details, see http://www.json.org/ 18 | /// 19 | /// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable. 20 | /// All numbers are parsed to doubles. 21 | /// 22 | public class MiniJSON 23 | { 24 | private const int TOKEN_NONE = 0; 25 | private const int TOKEN_CURLY_OPEN = 1; 26 | private const int TOKEN_CURLY_CLOSE = 2; 27 | private const int TOKEN_SQUARED_OPEN = 3; 28 | private const int TOKEN_SQUARED_CLOSE = 4; 29 | private const int TOKEN_COLON = 5; 30 | private const int TOKEN_COMMA = 6; 31 | private const int TOKEN_STRING = 7; 32 | private const int TOKEN_NUMBER = 8; 33 | private const int TOKEN_TRUE = 9; 34 | private const int TOKEN_FALSE = 10; 35 | private const int TOKEN_NULL = 11; 36 | private const int BUILDER_CAPACITY = 2000; 37 | 38 | /// 39 | /// On decoding, this value holds the position at which the parse failed (-1 = no error). 40 | /// 41 | protected static int lastErrorIndex = -1; 42 | protected static string lastDecode = ""; 43 | 44 | 45 | /// 46 | /// Parses the string json into a value 47 | /// 48 | /// A JSON string. 49 | /// An ArrayList, a Hashtable, a double, a string, null, true, or false 50 | public static object jsonDecode( string json ) 51 | { 52 | // save the string for debug information 53 | MiniJSON.lastDecode = json; 54 | 55 | if( json != null ) 56 | { 57 | char[] charArray = json.ToCharArray(); 58 | int index = 0; 59 | bool success = true; 60 | object value = MiniJSON.parseValue( charArray, ref index, ref success ); 61 | 62 | if( success ) 63 | MiniJSON.lastErrorIndex = -1; 64 | else 65 | MiniJSON.lastErrorIndex = index; 66 | 67 | return value; 68 | } 69 | else 70 | { 71 | return null; 72 | } 73 | } 74 | 75 | 76 | /// 77 | /// Converts a Hashtable / ArrayList / Dictionary(string,string) object into a JSON string 78 | /// 79 | /// A Hashtable / ArrayList 80 | /// A JSON encoded string, or null if object 'json' is not serializable 81 | public static string jsonEncode( object json ) 82 | { 83 | var builder = new StringBuilder( BUILDER_CAPACITY ); 84 | var success = MiniJSON.serializeValue( json, builder ); 85 | 86 | return ( success ? builder.ToString() : null ); 87 | } 88 | 89 | 90 | /// 91 | /// On decoding, this function returns the position at which the parse failed (-1 = no error). 92 | /// 93 | /// 94 | public static bool lastDecodeSuccessful() 95 | { 96 | return ( MiniJSON.lastErrorIndex == -1 ); 97 | } 98 | 99 | 100 | /// 101 | /// On decoding, this function returns the position at which the parse failed (-1 = no error). 102 | /// 103 | /// 104 | public static int getLastErrorIndex() 105 | { 106 | return MiniJSON.lastErrorIndex; 107 | } 108 | 109 | 110 | /// 111 | /// If a decoding error occurred, this function returns a piece of the JSON string 112 | /// at which the error took place. To ease debugging. 113 | /// 114 | /// 115 | public static string getLastErrorSnippet() 116 | { 117 | if( MiniJSON.lastErrorIndex == -1 ) 118 | { 119 | return ""; 120 | } 121 | else 122 | { 123 | int startIndex = MiniJSON.lastErrorIndex - 5; 124 | int endIndex = MiniJSON.lastErrorIndex + 15; 125 | if( startIndex < 0 ) 126 | startIndex = 0; 127 | 128 | if( endIndex >= MiniJSON.lastDecode.Length ) 129 | endIndex = MiniJSON.lastDecode.Length - 1; 130 | 131 | return MiniJSON.lastDecode.Substring( startIndex, endIndex - startIndex + 1 ); 132 | } 133 | } 134 | 135 | 136 | #region Parsing 137 | 138 | protected static Hashtable parseObject( char[] json, ref int index ) 139 | { 140 | Hashtable table = new Hashtable(); 141 | int token; 142 | 143 | // { 144 | nextToken( json, ref index ); 145 | 146 | bool done = false; 147 | while( !done ) 148 | { 149 | token = lookAhead( json, index ); 150 | if( token == MiniJSON.TOKEN_NONE ) 151 | { 152 | return null; 153 | } 154 | else if( token == MiniJSON.TOKEN_COMMA ) 155 | { 156 | nextToken( json, ref index ); 157 | } 158 | else if( token == MiniJSON.TOKEN_CURLY_CLOSE ) 159 | { 160 | nextToken( json, ref index ); 161 | return table; 162 | } 163 | else 164 | { 165 | // name 166 | string name = parseString( json, ref index ); 167 | if( name == null ) 168 | { 169 | return null; 170 | } 171 | 172 | // : 173 | token = nextToken( json, ref index ); 174 | if( token != MiniJSON.TOKEN_COLON ) 175 | return null; 176 | 177 | // value 178 | bool success = true; 179 | object value = parseValue( json, ref index, ref success ); 180 | if( !success ) 181 | return null; 182 | 183 | table[name] = value; 184 | } 185 | } 186 | 187 | return table; 188 | } 189 | 190 | 191 | protected static ArrayList parseArray( char[] json, ref int index ) 192 | { 193 | ArrayList array = new ArrayList(); 194 | 195 | // [ 196 | nextToken( json, ref index ); 197 | 198 | bool done = false; 199 | while( !done ) 200 | { 201 | int token = lookAhead( json, index ); 202 | if( token == MiniJSON.TOKEN_NONE ) 203 | { 204 | return null; 205 | } 206 | else if( token == MiniJSON.TOKEN_COMMA ) 207 | { 208 | nextToken( json, ref index ); 209 | } 210 | else if( token == MiniJSON.TOKEN_SQUARED_CLOSE ) 211 | { 212 | nextToken( json, ref index ); 213 | break; 214 | } 215 | else 216 | { 217 | bool success = true; 218 | object value = parseValue( json, ref index, ref success ); 219 | if( !success ) 220 | return null; 221 | 222 | array.Add( value ); 223 | } 224 | } 225 | 226 | return array; 227 | } 228 | 229 | 230 | protected static object parseValue( char[] json, ref int index, ref bool success ) 231 | { 232 | switch( lookAhead( json, index ) ) 233 | { 234 | case MiniJSON.TOKEN_STRING: 235 | return parseString( json, ref index ); 236 | case MiniJSON.TOKEN_NUMBER: 237 | return parseNumber( json, ref index ); 238 | case MiniJSON.TOKEN_CURLY_OPEN: 239 | return parseObject( json, ref index ); 240 | case MiniJSON.TOKEN_SQUARED_OPEN: 241 | return parseArray( json, ref index ); 242 | case MiniJSON.TOKEN_TRUE: 243 | nextToken( json, ref index ); 244 | return Boolean.Parse( "TRUE" ); 245 | case MiniJSON.TOKEN_FALSE: 246 | nextToken( json, ref index ); 247 | return Boolean.Parse( "FALSE" ); 248 | case MiniJSON.TOKEN_NULL: 249 | nextToken( json, ref index ); 250 | return null; 251 | case MiniJSON.TOKEN_NONE: 252 | break; 253 | } 254 | 255 | success = false; 256 | return null; 257 | } 258 | 259 | 260 | protected static string parseString( char[] json, ref int index ) 261 | { 262 | string s = ""; 263 | char c; 264 | 265 | eatWhitespace( json, ref index ); 266 | 267 | // " 268 | c = json[index++]; 269 | 270 | bool complete = false; 271 | while( !complete ) 272 | { 273 | if( index == json.Length ) 274 | break; 275 | 276 | c = json[index++]; 277 | if( c == '"' ) 278 | { 279 | complete = true; 280 | break; 281 | } 282 | else if( c == '\\' ) 283 | { 284 | if( index == json.Length ) 285 | break; 286 | 287 | c = json[index++]; 288 | if( c == '"' ) 289 | { 290 | s += '"'; 291 | } 292 | else if( c == '\\' ) 293 | { 294 | s += '\\'; 295 | } 296 | else if( c == '/' ) 297 | { 298 | s += '/'; 299 | } 300 | else if( c == 'b' ) 301 | { 302 | s += '\b'; 303 | } 304 | else if( c == 'f' ) 305 | { 306 | s += '\f'; 307 | } 308 | else if( c == 'n' ) 309 | { 310 | s += '\n'; 311 | } 312 | else if( c == 'r' ) 313 | { 314 | s += '\r'; 315 | } 316 | else if( c == 't' ) 317 | { 318 | s += '\t'; 319 | } 320 | else if( c == 'u' ) 321 | { 322 | int remainingLength = json.Length - index; 323 | if( remainingLength >= 4 ) 324 | { 325 | char[] unicodeCharArray = new char[4]; 326 | Array.Copy( json, index, unicodeCharArray, 0, 4 ); 327 | 328 | // Drop in the HTML markup for the unicode character 329 | s += "&#x" + new string( unicodeCharArray ) + ";"; 330 | 331 | /* 332 | uint codePoint = UInt32.Parse(new string(unicodeCharArray), NumberStyles.HexNumber); 333 | // convert the integer codepoint to a unicode char and add to string 334 | s += Char.ConvertFromUtf32((int)codePoint); 335 | */ 336 | 337 | // skip 4 chars 338 | index += 4; 339 | } 340 | else 341 | { 342 | break; 343 | } 344 | 345 | } 346 | } 347 | else 348 | { 349 | s += c; 350 | } 351 | 352 | } 353 | 354 | if( !complete ) 355 | return null; 356 | 357 | return s; 358 | } 359 | 360 | 361 | protected static double parseNumber( char[] json, ref int index ) 362 | { 363 | eatWhitespace( json, ref index ); 364 | 365 | int lastIndex = getLastIndexOfNumber( json, index ); 366 | int charLength = ( lastIndex - index ) + 1; 367 | char[] numberCharArray = new char[charLength]; 368 | 369 | Array.Copy( json, index, numberCharArray, 0, charLength ); 370 | index = lastIndex + 1; 371 | return Double.Parse( new string( numberCharArray ) ); // , CultureInfo.InvariantCulture); 372 | } 373 | 374 | 375 | protected static int getLastIndexOfNumber( char[] json, int index ) 376 | { 377 | int lastIndex; 378 | for( lastIndex = index; lastIndex < json.Length; lastIndex++ ) 379 | if( "0123456789+-.eE".IndexOf( json[lastIndex] ) == -1 ) 380 | { 381 | break; 382 | } 383 | return lastIndex - 1; 384 | } 385 | 386 | 387 | protected static void eatWhitespace( char[] json, ref int index ) 388 | { 389 | for( ; index < json.Length; index++ ) 390 | if( " \t\n\r".IndexOf( json[index] ) == -1 ) 391 | { 392 | break; 393 | } 394 | } 395 | 396 | 397 | protected static int lookAhead( char[] json, int index ) 398 | { 399 | int saveIndex = index; 400 | return nextToken( json, ref saveIndex ); 401 | } 402 | 403 | 404 | protected static int nextToken( char[] json, ref int index ) 405 | { 406 | eatWhitespace( json, ref index ); 407 | 408 | if( index == json.Length ) 409 | { 410 | return MiniJSON.TOKEN_NONE; 411 | } 412 | 413 | char c = json[index]; 414 | index++; 415 | switch( c ) 416 | { 417 | case '{': 418 | return MiniJSON.TOKEN_CURLY_OPEN; 419 | case '}': 420 | return MiniJSON.TOKEN_CURLY_CLOSE; 421 | case '[': 422 | return MiniJSON.TOKEN_SQUARED_OPEN; 423 | case ']': 424 | return MiniJSON.TOKEN_SQUARED_CLOSE; 425 | case ',': 426 | return MiniJSON.TOKEN_COMMA; 427 | case '"': 428 | return MiniJSON.TOKEN_STRING; 429 | case '0': 430 | case '1': 431 | case '2': 432 | case '3': 433 | case '4': 434 | case '5': 435 | case '6': 436 | case '7': 437 | case '8': 438 | case '9': 439 | case '-': 440 | return MiniJSON.TOKEN_NUMBER; 441 | case ':': 442 | return MiniJSON.TOKEN_COLON; 443 | } 444 | index--; 445 | 446 | int remainingLength = json.Length - index; 447 | 448 | // false 449 | if( remainingLength >= 5 ) 450 | { 451 | if( json[index] == 'f' && 452 | json[index + 1] == 'a' && 453 | json[index + 2] == 'l' && 454 | json[index + 3] == 's' && 455 | json[index + 4] == 'e' ) 456 | { 457 | index += 5; 458 | return MiniJSON.TOKEN_FALSE; 459 | } 460 | } 461 | 462 | // true 463 | if( remainingLength >= 4 ) 464 | { 465 | if( json[index] == 't' && 466 | json[index + 1] == 'r' && 467 | json[index + 2] == 'u' && 468 | json[index + 3] == 'e' ) 469 | { 470 | index += 4; 471 | return MiniJSON.TOKEN_TRUE; 472 | } 473 | } 474 | 475 | // null 476 | if( remainingLength >= 4 ) 477 | { 478 | if( json[index] == 'n' && 479 | json[index + 1] == 'u' && 480 | json[index + 2] == 'l' && 481 | json[index + 3] == 'l' ) 482 | { 483 | index += 4; 484 | return MiniJSON.TOKEN_NULL; 485 | } 486 | } 487 | 488 | return MiniJSON.TOKEN_NONE; 489 | } 490 | 491 | #endregion 492 | 493 | 494 | #region Serialization 495 | 496 | protected static bool serializeObjectOrArray( object objectOrArray, StringBuilder builder ) 497 | { 498 | if( objectOrArray is Hashtable ) 499 | { 500 | return serializeObject( (Hashtable)objectOrArray, builder ); 501 | } 502 | else if( objectOrArray is ArrayList ) 503 | { 504 | return serializeArray( (ArrayList)objectOrArray, builder ); 505 | } 506 | else 507 | { 508 | return false; 509 | } 510 | } 511 | 512 | 513 | protected static bool serializeObject( Hashtable anObject, StringBuilder builder ) 514 | { 515 | builder.Append( "{" ); 516 | 517 | IDictionaryEnumerator e = anObject.GetEnumerator(); 518 | bool first = true; 519 | while( e.MoveNext() ) 520 | { 521 | string key = e.Key.ToString(); 522 | object value = e.Value; 523 | 524 | if( !first ) 525 | { 526 | builder.Append( ", " ); 527 | } 528 | 529 | serializeString( key, builder ); 530 | builder.Append( ":" ); 531 | if( !serializeValue( value, builder ) ) 532 | { 533 | return false; 534 | } 535 | 536 | first = false; 537 | } 538 | 539 | builder.Append( "}" ); 540 | return true; 541 | } 542 | 543 | 544 | protected static bool serializeDictionary( Dictionary dict, StringBuilder builder ) 545 | { 546 | builder.Append( "{" ); 547 | 548 | bool first = true; 549 | foreach( var kv in dict ) 550 | { 551 | if( !first ) 552 | builder.Append( ", " ); 553 | 554 | serializeString( kv.Key, builder ); 555 | builder.Append( ":" ); 556 | serializeString( kv.Value, builder ); 557 | 558 | first = false; 559 | } 560 | 561 | builder.Append( "}" ); 562 | return true; 563 | } 564 | 565 | 566 | protected static bool serializeArray( ArrayList anArray, StringBuilder builder ) 567 | { 568 | builder.Append( "[" ); 569 | 570 | bool first = true; 571 | for( int i = 0; i < anArray.Count; i++ ) 572 | { 573 | object value = anArray[i]; 574 | 575 | if( !first ) 576 | { 577 | builder.Append( ", " ); 578 | } 579 | 580 | if( !serializeValue( value, builder ) ) 581 | { 582 | return false; 583 | } 584 | 585 | first = false; 586 | } 587 | 588 | builder.Append( "]" ); 589 | return true; 590 | } 591 | 592 | 593 | protected static bool serializeValue( object value, StringBuilder builder ) 594 | { 595 | // Type t = value.GetType(); 596 | // Debug.Log("type: " + t.ToString() + " isArray: " + t.IsArray); 597 | 598 | if( value == null ) 599 | { 600 | builder.Append( "null" ); 601 | } 602 | else if( value.GetType().IsArray ) 603 | { 604 | serializeArray( new ArrayList( (ICollection)value ), builder ); 605 | } 606 | else if( value is string ) 607 | { 608 | serializeString( (string)value, builder ); 609 | } 610 | else if( value is Char ) 611 | { 612 | serializeString( Convert.ToString( (char)value ), builder ); 613 | } 614 | else if( value is Hashtable ) 615 | { 616 | serializeObject( (Hashtable)value, builder ); 617 | } 618 | else if( value is Dictionary ) 619 | { 620 | serializeDictionary( (Dictionary)value, builder ); 621 | } 622 | else if( value is ArrayList ) 623 | { 624 | serializeArray( (ArrayList)value, builder ); 625 | } 626 | else if( ( value is Boolean ) && ( (Boolean)value == true ) ) 627 | { 628 | builder.Append( "true" ); 629 | } 630 | else if( ( value is Boolean ) && ( (Boolean)value == false ) ) 631 | { 632 | builder.Append( "false" ); 633 | } 634 | else if( value.GetType().IsPrimitive ) 635 | { 636 | serializeNumber( Convert.ToDouble( value ), builder ); 637 | } 638 | else 639 | { 640 | return false; 641 | } 642 | 643 | return true; 644 | } 645 | 646 | 647 | protected static void serializeString( string aString, StringBuilder builder ) 648 | { 649 | builder.Append( "\"" ); 650 | 651 | char[] charArray = aString.ToCharArray(); 652 | for( int i = 0; i < charArray.Length; i++ ) 653 | { 654 | char c = charArray[i]; 655 | if( c == '"' ) 656 | { 657 | builder.Append( "\\\"" ); 658 | } 659 | else if( c == '\\' ) 660 | { 661 | builder.Append( "\\\\" ); 662 | } 663 | else if( c == '\b' ) 664 | { 665 | builder.Append( "\\b" ); 666 | } 667 | else if( c == '\f' ) 668 | { 669 | builder.Append( "\\f" ); 670 | } 671 | else if( c == '\n' ) 672 | { 673 | builder.Append( "\\n" ); 674 | } 675 | else if( c == '\r' ) 676 | { 677 | builder.Append( "\\r" ); 678 | } 679 | else if( c == '\t' ) 680 | { 681 | builder.Append( "\\t" ); 682 | } 683 | else 684 | { 685 | int codepoint = Convert.ToInt32( c ); 686 | if( ( codepoint >= 32 ) && ( codepoint <= 126 ) ) 687 | { 688 | builder.Append( c ); 689 | } 690 | else 691 | { 692 | builder.Append( "\\u" + Convert.ToString( codepoint, 16 ).PadLeft( 4, '0' ) ); 693 | } 694 | } 695 | } 696 | 697 | builder.Append( "\"" ); 698 | } 699 | 700 | 701 | protected static void serializeNumber( double number, StringBuilder builder ) 702 | { 703 | builder.Append( Convert.ToString( number ) ); // , CultureInfo.InvariantCulture)); 704 | } 705 | 706 | #endregion 707 | 708 | } 709 | 710 | 711 | 712 | #region Extension methods 713 | 714 | public static class MiniJsonExtensions 715 | { 716 | public static string toJson( this Hashtable obj ) 717 | { 718 | return MiniJSON.jsonEncode( obj ); 719 | } 720 | 721 | 722 | public static string toJson( this Dictionary obj ) 723 | { 724 | return MiniJSON.jsonEncode( obj ); 725 | } 726 | 727 | 728 | public static ArrayList arrayListFromJson( this string json ) 729 | { 730 | return MiniJSON.jsonDecode( json ) as ArrayList; 731 | } 732 | 733 | 734 | public static Hashtable hashtableFromJson( this string json ) 735 | { 736 | return MiniJSON.jsonDecode( json ) as Hashtable; 737 | } 738 | } 739 | 740 | #endregion 741 | } --------------------------------------------------------------------------------