#!/bin/bash

#set -e表示一旦脚本中有命令的返回值为非0,则脚本立即退出,后续命令不再执行;
#set -o pipefail表示在管道连接的命令序列中,只要有任何一个命令返回非0值,则整个管道返回非0值,即使最后一个命令返回0.
export setCmd="set -eo pipefail"
$setCmd

#导出环境变量
export PATH=/opt/MonkeyDev/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin:$PATH

#脚本名称和版本
export scriptName="${0##*/}"
export scriptVer="2.0"

#本地存储文件的目录
export MonkeyDevPath="/opt/MonkeyDev"
export backupFileExt=".MonkeyDev"

#获取用户名、用户组、用户目录、和profile文件
export userName="${SUDO_USER-$USER}"
export userGroup=`id -g $userName`
export userHome=`eval echo ~$userName`

#用户可能存在的profile文件
export bashProfileFiles=("$userHome/.zshrc" "$userHome/.bash_profile" "$userHome/.bashrc" "$userHome/.bash_login" "$userHome/.profile")

#获取临时文件名
export tempDirsFile="`mktemp -d -t $scriptName`/tempdirs"
touch "$tempDirsFile"

#把LANG变量从当前环境中删除
unset LANG

#出错退出
function cleanup()
{
	local exitCode=$?
	set +e
	trap - $signals
	removeTempData
	exit $exitCode
}

function panic()
{
	local exitCode=$1
	set +e
	shift
	[[ "$@" == "" ]] || echo "$@" >&2
	exit $exitCode
}

export signals="0 1 2 3 15"
#当shell接收到signals指定的信号时,执行cleanup命令
trap cleanup $signals

function removeTempData()
{
	local tempDirs
	if [[ -f "$tempDirsFile" ]]; then
		tempDirs=(`cat "$tempDirsFile"`)
		for td in "${tempDirs[@]}"; do
			rm -rf "$td" || true
		done
		rm -rf "`dirname $tempDirsFile`" || true
	fi
}
function getTempDir()
{
	$setCmd
	local tempDir
	tempDir=`mktemp -d -t $scriptName` || \
		panic $? "Failed to create temporary directory"
	echo "$tempDir" >> "$tempDirsFile" || \
		panic $? "Failed to echo into $tempDirsFile"
	echo "$tempDir"
}

function copyFile()
{
	cp -f "$1" "$2" || \
		panic $? "Failed to copy file $1 to $2"
}

#备份原文件
function requireBackup()
{
	[[ ! -f "$1" || -f "${1}${backupFileExt}" ]] || \
		copyFile "$1" "${1}${backupFileExt}"
}

#获取SDK信息
function getSdkProperty()
{
	$setCmd

	local sdk="$1"
	local propertyName="$2"

	propertyValue=`xcodebuild -version -sdk $sdk $propertyName` || \
		panic $? "Failed to get $sdk SDK property $propertyName"

	[[ $propertyValue != "" ]] || \
		panic 1 "Value of $sdk SDK property $propertyName cannot be empty"

	# return #
	echo "$propertyValue"
}

#下载文件
function downloadFile() # args: sourceUrl, targetPath
{
	local sourceUrl="$1"
	local targetPath="$2"
	local curlPath

	mkdir -p "${targetPath%/*}" || \
		panic $? "Failed to make directory: ${targetPath%/*}"

	curlPath=`which curl` || \
		panic $? "Failed to get curl path"

	"$curlPath" --output "$targetPath" "$sourceUrl" || \
		panic $? "Failed to download $sourceUrl to $targetPath"
}

#解压文件
function extractTar() # args: tarPath, outputPath
{
	local tarPath="$1"
	local outputPath="$2"
	
	tar -C "$outputPath" -zxf "$tarPath" || \
		panic $? "Failed to extract $tarPath to $outputPath"
}

#下载github文件
function downloadGithubTarball() # args: url, outputDir, title
{
	$setcmd

	local url="$1"
	local outputDir="$2"
	local title="$3"
	local tempDirForTar
	local tempDirForFiles
	local untardDir
	local tarFile="file.tar.gz"

	echo "Downloading $title from Github..."

	tempDirForTar=`getTempDir`
	tempDirForFiles=`getTempDir`
	
	downloadFile "$url" "$tempDirForTar/$tarFile"
	
	extractTar "$tempDirForTar/$tarFile" "$tempDirForFiles"

	untardDir=`find "$tempDirForFiles/"* -type d -depth 0` || \
		panic $? "Failed to get untar'ed directory name of $tempDirForTar/$tarFile"

	mkdir -p "$outputDir" || \
		panic $? "Failed to make directory: $outputDir"

	cp -fR "$untardDir/"* "$outputDir/"
}

#修改文件权限
function changeMode()
{
	local mode="$1"
	local target="$2"
	local recursive="$3"
	local options

	[[ $recursive != "true" ]] || \
		options="-R"

	if [[ -e "$target" ]]; then
		chmod $options "$mode" "$target" || \
			panic $? "Failed to change mode to $mode on $target"
	fi
}

#获取用户profile文件
function determineUserBashProfileFile()
{
	$setCmd

	local f
	local filePath
	
	for f in "${bashProfileFiles[@]}"; do
		if [[ -f "$f" ]]; then
			filePath="$f"
			echo "" >> "$f" || \
				panic $? "Failed to echo into $f"
			break
		fi
	done
	
	if [[ $filePath == "" ]]; then
		filePath="$bashProfileFiles"

		touch "$filePath" || \
			panic $? "Failed to touch $filePath"
			
		chown "$userName:$userGroup" "$filePath" || \
			panic $? "Failed to change owner-group of $filePath"
		
		changeMode 0600 "$filePath"
	fi
	
	# return #
	echo "$filePath"
}

#验证是否存在文件
function requireFile() # args: filePath [, touchFileIfNotFound]
{
	local filePath="$1"
	local touchFileIfNotFound="$2"
	
	if [[ ! -f "$filePath" ]]; then
		if [[ $touchFileIfNotFound == "true" ]]; then

			touch "$filePath" || \
				panic $? "Failed to touch $filePath"
				
		else
			panic 1 "File $filePath not found"
		fi
	fi
}

#增加内容到文件
function addToFileIfMissing() # args: filePath, pattern, value
{
	local filePath="$1"
	local pattern="$2"
	local value="$3"
	local doesContain

	doesContain=`doesFileContain "$filePath" "$pattern"`
	
	[[ $doesContain == "true" ]] || \
		echo "$value" >> "$filePath" || \
			panic $? "Failed to echo into $filePath"	
}

#判断文件是否包含内容
function doesFileContain() # args: filePath, pattern
{
	$setCmd
	
	local filePath="$1"
	local pattern="$2"
	local perlValue
	local funcReturn
	
	perlValue=`perl -ne 'if (/'"$pattern"'/) { print "true"; exit; }' "$filePath"` || \
		panic $? "Failed to perl"

	if [[ $perlValue == "true" ]]; then
		funcReturn="true"
	else
		funcReturn="false"
	fi
	
	# return #
	echo $funcReturn
}

#从spec读取内容
function readXcodeSpecificationById(){ #args: filePath, id
	local filePath="$1"
	local id="$2"
	content=`/usr/libexec/PlistBuddy -x -c Print "$filePath"` || \
		panic $? "Failed to get $filePath content"
	for (( i=0; i<=1; i++)); do
		dict=`/usr/libexec/PlistBuddy -x -c "Print $i" "$filePath"`
		if echo $dict | grep -qE "<string>$id</string>"; then
			echo "$dict"
		fi
	done
}

#往spec文件写入内容
function writeDictToSpecification(){ #args: filePath, content
	local filePath="$1"
	local content="$2"
	tempfile=`getTempDir`/dictfile
	echo "$content" >> $tempfile
	/usr/libexec/PlistBuddy -x -c 'add 0 dict' "$filePath" > /dev/null
	/usr/libexec/PlistBuddy -x -c "merge $tempfile 0" "$filePath" > /dev/null
}

# start it
# 创建/opt/MonkeyDev
mkdir -p "$MonkeyDevPath" || \
	panic $? "Failed to make directory: $MonkeyDevPath"

branch="master"

if [[ "$1" ]]; then
	branch="$1"
fi

#下载一些基础文件和模板文件
downloadGithubTarball "https://codeload.github.com/AloneMonkey/MonkeyDev/tar.gz/$branch" "$MonkeyDevPath" "MonkeyDev base"
downloadGithubTarball "https://codeload.github.com/AloneMonkey/MonkeyDev-Xcode-Templates/tar.gz/$branch" "$MonkeyDevPath/templates" "Xcode templates"

#下载frida-ios-dump
echo "Downloading frida-ios-dump from Github..."
downloadFile "https://raw.githubusercontent.com/AloneMonkey/frida-ios-dump/3.x/dump.py" "$MonkeyDevPath/bin/dump.py"
downloadFile "https://raw.githubusercontent.com/AloneMonkey/frida-ios-dump/3.x/dump.js" "$MonkeyDevPath/bin/dump.js"

chmod +x "$MonkeyDevPath/bin/dump.py"

#创建符号链接
echo "Creating symlink to Xcode templates..."

#$userHome/Library/Developer/Xcode/Templates/MonkeyDev linkto $MonkeyDevPath/templates
userDevDir="$userHome/Library/Developer"
userTemplatesDir="$userDevDir/Xcode/Templates"

if [[ ! -d "$userTemplatesDir" ]]; then
	mkdir -p "$userTemplatesDir" || \
		panic $? "Failed to make directory: $userTemplatesDir"
		
	chown -R "$userName:$userGroup" "$userDevDir" || \
		panic $? "Failed to change ownership-group of $userDevDir"
fi

ln -fhs "$MonkeyDevPath/templates" "$userTemplatesDir/MonkeyDev"

#修改用户profile文件
echo "Modifying Bash personal initialization file..."

userBashProfileFile=`determineUserBashProfileFile`

addToFileIfMissing "$userBashProfileFile" "^(export)? *MonkeyDevPath=.*" "export MonkeyDevPath=$MonkeyDevPath"
addToFileIfMissing "$userBashProfileFile" "^(export)? *MonkeyDevDeviceIP=.*" "export MonkeyDevDeviceIP="
addToFileIfMissing "$userBashProfileFile" "^(export)? *PATH=.*(\\\$MonkeyDevPath\\/bin|${MonkeyDevPath//\//\\/}\\/bin).*" "export PATH=$MonkeyDevPath/bin:\$PATH"

#支持iphoneos command line tools
iosSdkPlatformPath=`getSdkProperty iphoneos PlatformPath`
macosSdkPlatformPath=`getSdkProperty macosx PlatformPath`

specificationFile=$(cd $iosSdkPlatformPath/../../.. && pwd)/PlugIns/IDEiOSSupportCore.ideplugin/Contents/Resources/Embedded-Device.xcspec

requireFile "$specificationFile" false

#backup
requireBackup "$specificationFile"

hasPackageTypeForCommandLineTool=`doesFileContain "$specificationFile" 'com.apple.package-type.mach-o-executable'`
hasProductTypeForCommandLineTool=`doesFileContain "$specificationFile" 'com.apple.product-type.tool'`

macosxSDKSpecificationsPath=$macosSdkPlatformPath/Developer/Library/Xcode/Specifications
packageTypesForMacOSXPath="$macosxSDKSpecificationsPath/MacOSX Package Types.xcspec"
productTypesForMacOSXPath="$macosxSDKSpecificationsPath/MacOSX Product Types.xcspec"

requireFile "$packageTypesForMacOSXPath" false
requireFile "$productTypesForMacOSXPath" false

if [[ $hasPackageTypeForCommandLineTool != "true" ]]; then
	machoDict=`readXcodeSpecificationById "$packageTypesForMacOSXPath" "com.apple.package-type.mach-o-executable"`
	writeDictToSpecification "$specificationFile" "$machoDict"
fi

if [[ $hasProductTypeForCommandLineTool != "true" ]]; then
	toolDict=`readXcodeSpecificationById "$productTypesForMacOSXPath" "com.apple.product-type.tool"`
	writeDictToSpecification "$specificationFile" "$toolDict"
fi

exit 0