update_gi.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. #!/usr/bin/env bash
  2. # Checker: shellcheck --shell=sh --exclude=SC2006,SC3043 update_gi.sh | less
  3. # Exit on error.
  4. set -e
  5. fatal() {
  6. echo
  7. for arg in "$@"; do
  8. echo " * $arg" >&2
  9. done
  10. echo
  11. exit 1
  12. }
  13. # ======== Global constants
  14. UPDATE_URL_OS='https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?channel_id=1&key=gcStgarh&launcher_id=10'
  15. UPDATE_URL_CN='https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/resource?channel_id=1&key=eYd89JmJ&launcher_id=18'
  16. UPDATE_URL_BB='https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/resource?channel_id=14&key=KAtdSsoQ&launcher_id=17'
  17. #UPDATE_URL_OS='http://127.0.0.1:8000/dummy/resource.json?arg1=foo&arg2=bar'
  18. CONFIG_FILE='config.ini'
  19. ANCHOR_FILE='UnityPlayer.dll'
  20. # ======== Voice pack constants
  21. LANG_PACKS_PATH_OS='GenshinImpact_Data/StreamingAssets/Audio/GeneratedSoundBanks/Windows'
  22. LANG_PACKS_PATH_CN='YuanShen_Data/StreamingAssets/Audio/GeneratedSoundBanks/Windows'
  23. LANG_PACKS_PATH_BB=$LANG_PACKS_PATH_CN
  24. LANG_MAP_ENGLISHUS='en-us'
  25. LANG_MAP_JAPANESE='ja-jp'
  26. LANG_MAP_KOREAN='ko-kr'
  27. LANG_MAP_CHINESE='zh-cn'
  28. # ======== Evaluated variables
  29. THIS_FILE=`basename "$0"`
  30. THIS_PATH=`realpath "$(dirname "$0")"`
  31. REPO_PATH=`dirname "$THIS_PATH"` # parent
  32. reltype="" # OS, CN or BB. filled later.
  33. remove_archives=1 # 0 or 1. filled later.
  34. # ======== Dependency checks
  35. # Check is all required tools installed.
  36. for appname in jq bash unzip xdelta3; do
  37. exepath=`command -v "$appname" | tee`
  38. [ ! -e "$exepath" ] && fatal \
  39. "Required tool not found!" \
  40. "Please install: ${appname}."
  41. done
  42. # ======== Download tool setup
  43. DOWNLOAD_PATH="../_update_gi_download"
  44. #DOWNLOAD_PATH="../download with spaces"
  45. DL_APP_ARGS_axel='-n 15'
  46. DL_APP_ARGS_aria2c='--no-conf -c'
  47. DL_APP_ARGS_fetch='--force-restart --no-mtime --retry --keep-output --restart'
  48. DL_APP_ARGS_wget='-c'
  49. DL_APP_ARGS_curl='--disable -C -'
  50. # Find first available download tool.
  51. for appname in axel aria2c fetch wget curl; do
  52. # Pipe to "tee" overwrites the exit status
  53. exepath=`command -v "$appname" | tee`
  54. if [ -e "$exepath" ]; then
  55. DL_APP_BIN="$exepath"
  56. eval DL_APP_ARGS="\$DL_APP_ARGS_${appname}"
  57. break
  58. fi
  59. done
  60. [ ! -e "$DL_APP_BIN" ] && fatal \
  61. "No downloader application found." \
  62. "Please install one of: ${DL_APPS_LIST}."
  63. echo "--- Using download application: ${DL_APP_BIN} ${DL_APP_ARGS}"
  64. # ======== Functions
  65. # MacOS and *BSD do not have md5sum: use md5 instead
  66. exepath=$(command -v md5 | tee)
  67. if [ -e "$exepath" ]; then
  68. md5check() {
  69. # 1 = Checksum, 2 = File
  70. md5 -q -c "$1" "$2" >/dev/null 2>&1
  71. }
  72. else
  73. md5check() {
  74. # 1 = Checksum, 2 = File
  75. local input=`echo "$1" | tr 'A-Z' 'a-z'`
  76. local filesum=`md5sum "$2" | cut -d ' ' -f1`
  77. if [ "$input" != "$filesum" ]; then
  78. echo "Mismatch!"
  79. exit 1
  80. fi
  81. }
  82. fi
  83. download_file() {
  84. local url="$1"
  85. local dst_path="$2"
  86. local md5="$3"
  87. local filename_args="${dst_path}/${url##*/}"
  88. local filename="${filename_args%%\?*}"
  89. local filename_completed="${filename}.completed"
  90. echo
  91. mkdir -p "$dst_path"
  92. if [ -f "$filename_completed" ]; then
  93. echo "--> Skipping: Already downloaded."
  94. else
  95. (cd "$dst_path" && "$DL_APP_BIN" $DL_APP_ARGS "$url")
  96. if [ -n "$md5" ]; then
  97. echo -n '--- Verifying MD5 checksum ... '
  98. md5check "$md5" "$filename"
  99. echo 'OK'
  100. touch "$filename_completed"
  101. fi
  102. fi
  103. }
  104. # Approximate size of the installed archive
  105. download_json_size() {
  106. local size=`echo "$1" | jq -r -M ".size"`
  107. size="$((($size + 1048576) / 1024 / 1024 / 2))"
  108. echo "~${size} MiB"
  109. }
  110. download_json_section() {
  111. local json_text="$1"
  112. local url=`echo "$json_text" | jq -r -M ".path"`
  113. local md5=`echo "$json_text" | jq -r -M ".md5"`
  114. download_file "$url" "$DOWNLOAD_PATH" "$md5"
  115. }
  116. get_ini_value() {
  117. local filename="${1}"
  118. local variable="${2}"
  119. grep "${variable}=" "${filename}" | tr -d '\r\n' | sed -e 's|.*=||g'
  120. }
  121. # ======== Path sanity checks
  122. # There is a good reason for this check. Do not pollute the game directory.
  123. [ -e "${THIS_PATH}/${ANCHOR_FILE}" ] && fatal \
  124. "Please move this script outside the game directory prior executing." \
  125. " -> See README.md for proper installation instructions"
  126. # In case people accidentally want to install the game into the launcher directory.
  127. [ `find ./*.dll 2>/dev/null | wc -l` -gt 2 ] && fatal \
  128. "This script is likely run in the wrong directory." \
  129. "Found more than two DLL files. (expected: 0...2)" \
  130. "Please run this script in a proper/clean game directory."
  131. # ======== Command line processing
  132. cli_help=0
  133. cli_install=0
  134. for arg in "$@"; do
  135. case "$arg" in
  136. -h|--help|help)
  137. cli_help=1
  138. ;;
  139. install)
  140. cli_install=1
  141. ;;
  142. nodelete)
  143. remove_archives=0
  144. echo "--- Archives will not be deleted after download"
  145. ;;
  146. *)
  147. fatal "Unknown option: ${arg}"
  148. esac
  149. done
  150. if [ "$cli_help" -eq 1 ]; then
  151. cat << HELP
  152. ${THIS_FILE} [-h|help] [install] [nodelete]
  153. This script will modify the current working directory.
  154. See README.md for details and examples.
  155. "install" : new game installation from scratch
  156. "nodelete" : whether to keep the downloaded archives
  157. HELP
  158. exit 0
  159. fi
  160. # New game installation option
  161. if [ "$cli_install" -eq 1 ]; then
  162. if [ `find ./* 2>/dev/null | wc -l` -gt 0 ]; then
  163. fatal "'${PWD}' contains files and/or subdirectories." \
  164. "Please use an empty directory for a new installation." \
  165. "To update or resume an installation, rerun this script without the 'install' argument."
  166. fi
  167. echo ""
  168. echo " 0 -> Genshin Impact [America/Europe/Asia/TW,HK,MO]"
  169. echo " 1 -> YuanShen [Mainland China]"
  170. echo " 2 -> BiliBili [Mainland China]"
  171. read -p "Which version shall be installed? [0]/1/2: " choice
  172. if [[ -z "$choice" || "$choice" == "0" ]]; then
  173. reltype="OS"
  174. echo -ne '[General]\r\nchannel=1\r\ncps=mihoyo\r\ngame_version=0.0.0\r\nsdk_version=\r\nsub_channel=0\r\n' >"$CONFIG_FILE"
  175. elif [ "$choice" = "1" ]; then
  176. reltype="CN"
  177. echo -ne '[General]\r\nchannel=1\r\ncps=mihoyo\r\ngame_version=0.0.0\r\nsdk_version=\r\nsub_channel=1\r\n' >"$CONFIG_FILE"
  178. elif [ "$choice" = "2" ]; then
  179. reltype="BB"
  180. echo -ne '[General]\r\nchannel=14\r\ncps=bilibili\r\ngame_version=0.0.0\r\nsdk_version=\r\nsub_channel=0\r\n' >"$CONFIG_FILE"
  181. else
  182. fatal "Invalid selection"
  183. fi
  184. else
  185. # Check for existing installation
  186. if [ -e "GenshinImpact.exe" ]; then
  187. reltype="OS"
  188. elif [ -e "YuanShen.exe" ]; then
  189. if [ -e "$DATADIR/Plugins/PCGameSDK.dll" ]; then
  190. reltype="BB"
  191. else
  192. reltype="CN"
  193. fi
  194. fi
  195. fi
  196. # ======== Configuration file parsing
  197. game_not_found_message=(
  198. "Make sure 'Genshin Impact Game' is the current working directory."
  199. "If you would like to install the game append the 'install' option:"
  200. "bash '${THIS_PATH}/${THIS_FILE}' install"
  201. )
  202. [ -z "$reltype" ] && fatal \
  203. "Cannot determine the installed game type." \
  204. "${game_not_found_message[@]}"
  205. eval LANG_PACKS_PATH="\$LANG_PACKS_PATH_${reltype}"
  206. eval UPDATE_URL="\$UPDATE_URL_${reltype}"
  207. # $reltype is no longer used
  208. [ ! -e "$CONFIG_FILE" ] && fatal \
  209. "Game information file ${CONFIG_FILE} not found." \
  210. "${game_not_found_message[@]}"
  211. installed_ver=`get_ini_value "${CONFIG_FILE}" 'game_version'`
  212. [ -z "$installed_ver" ] && fatal \
  213. "Cannot read game_version from ${CONFIG_FILE}. File corrupt?"
  214. echo "--- Installed version: ${installed_ver}"
  215. # ======== Update information download + meta checks
  216. # WARNING: File cannot be downloaded to NTFS/FAT* partitions due to special characters
  217. tmp_path=`mktemp -d`
  218. trap "rm -rf '$tmp_path'" EXIT
  219. download_file "$UPDATE_URL" "$tmp_path"
  220. RESOURCE_FILE=`find "$tmp_path" -name 'resource*' | tee`
  221. [ ! -f "${RESOURCE_FILE}" ] && fatal \
  222. "Failed to download version info. Check your internet connection."
  223. upstream_ver=`jq -r -M '.data .game .latest .version' "$RESOURCE_FILE" | tee`
  224. echo "--- Latest version: ${upstream_ver}"
  225. if [ "$upstream_ver" = "$installed_ver" ]; then
  226. echo
  227. echo "==> Client is up to date."
  228. exit 0
  229. fi
  230. # Check whether this version can be patched
  231. patcher_dir="$REPO_PATH"/`echo "$upstream_ver" | tr -d .`
  232. [ ! -d "$patcher_dir" ] && fatal \
  233. "No suitable patch script found. Please check the bug tracker for details about the progress."
  234. # ======== Select update type
  235. # Check is diff update possible.
  236. archive_json=`jq -r -M ".data .game .diffs[] | select(.version==\"${installed_ver}\")" "$RESOURCE_FILE"`
  237. if [ -z "$archive_json" ]; then
  238. # Fallback to full download.
  239. archive_json=`jq -r -M '.data .game .latest' "$RESOURCE_FILE"`
  240. dl_type="new installation"
  241. else
  242. dl_type="incremental update"
  243. fi
  244. size=`download_json_size "$archive_json"`
  245. echo "Download type: ${dl_type} (${size})"
  246. # Confirm install/update.
  247. while :; do
  248. read -r -p "Start/continue update? [Y]/n: " input
  249. #input="y"
  250. case "$input" in
  251. Y|y|'')
  252. echo
  253. break
  254. ;;
  255. n|N)
  256. exit 0
  257. ;;
  258. esac
  259. done
  260. # Download SDK if exists
  261. sdk_json=`jq -r -M ".data .sdk | select(.!=null)" "$RESOURCE_FILE"`
  262. if [ -n "$sdk_json" ]; then
  263. download_json_section "$sdk_json"
  264. fi
  265. # Download main game archive
  266. download_json_section "$archive_json"
  267. # ======== Locate and install voiceover packs
  268. if [ -d "$LANG_PACKS_PATH" ]; then
  269. # Get voiceover directory name in capitals. Does proper space handling.
  270. lang_dir_names=$(find "$LANG_PACKS_PATH" -mindepth 1 -type d | xargs -L1 -r basename | tr -d '()' | tr 'a-z' 'A-Z')
  271. fi
  272. # Download langs packs.
  273. echo "$lang_dir_names" | while read dir_name; do
  274. [ -z "$dir_name" ] && continue
  275. eval lang_code="\$LANG_MAP_${dir_name}"
  276. lang_archive_json=`echo ${archive_json} | jq -r -M ".voice_packs[] | select(.language==\"${lang_code}\")"`
  277. if [ "$lang_archive_json" = 'null' -o "$lang_archive_json" = '' ]; then
  278. echo "--- Cannot find update for language: ${dir_name}"
  279. continue
  280. fi
  281. size=`download_json_size "$lang_archive_json"`
  282. echo "--- Voiceover pack: ${lang_code} (${size})"
  283. download_json_section "$lang_archive_json"
  284. done
  285. # ======== Revert patch & apply update
  286. # Run 'patch_revert.sh' on update existing installation.
  287. if [ -e "$ANCHOR_FILE" ]; then
  288. echo
  289. echo "============== Reverting previous Wine patch ==============="
  290. bash "${patcher_dir}/patch_revert.sh"
  291. echo "============================================================"
  292. echo
  293. fi
  294. # Unpack the game files and remove old ones according to deletefiles.txt
  295. echo "--- Updating game files...."
  296. find "${DOWNLOAD_PATH}" -name "*.zip" | while read archive_name; do
  297. bash "${THIS_PATH}/perform_update.sh" "$archive_name"
  298. done
  299. if [ "$remove_archives" -eq 1 ]; then
  300. # Remove downloads when all updates succeeded
  301. # otherwise keep the archives to fix with "perform_update.sh" manually
  302. find "${DOWNLOAD_PATH}" -name "*.zip" | while read archive_name; do
  303. rm -f "${archive_name}" "${archive_name}.completed"
  304. done
  305. fi
  306. # ======== Config update and game patching
  307. # Update version in config file.
  308. sed -i "s/game_version=${installed_ver}/game_version=${upstream_ver}/" "$CONFIG_FILE"
  309. if [ -n "$sdk_json" ]; then
  310. sdk_version=`echo "$sdk_json" | jq -r -M ".version"`
  311. sed -i "s/^sdk_version=.*/sdk_version=${sdk_version}/" "$CONFIG_FILE"
  312. fi
  313. if [ "$remove_archives" -eq 1 ]; then
  314. # The directory should be empty now. Remove.
  315. rm -r "$DOWNLOAD_PATH"
  316. fi
  317. echo
  318. echo "==> Update to version ${upstream_ver} completed"
  319. # Run wine compatibility patch script.
  320. echo
  321. echo "================= Applying new Wine patch =================="
  322. bash "${patcher_dir}/patch.sh"
  323. echo "============================================================"
  324. echo "==> Update script completed successfully"
  325. exit 0