update_gi.sh 13 KB

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