update_gi.sh 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. #!/usr/bin/sh
  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='https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?key=gcStgarh&launcher_id=10'
  15. #UPDATE_URL='http://127.0.0.1:8000/dummy/resource.json?arg1=foo&arg2=bar'
  16. CONFIG_FILE='config.ini'
  17. ANCHOR_FILE='UnityPlayer.dll'
  18. # ======== Voice pack constants
  19. LANG_PACKS_PATH='GenshinImpact_Data/StreamingAssets/Audio/GeneratedSoundBanks/Windows'
  20. LANG_MAP_ENGLISHUS='en-us'
  21. LANG_MAP_JAPANESE='ja-jp'
  22. LANG_MAP_KOREAN='ko-kr'
  23. LANG_MAP_CHINESE='zh-cn'
  24. # ======== Evaluated variables
  25. THIS_FILE=`basename "$0"`
  26. THIS_PATH=`realpath "$(dirname "$0")"`
  27. REPO_PATH=`dirname "$THIS_PATH"` # parent
  28. # ======== Dependency checks
  29. # Check is all required tools installed.
  30. for appname in jq bash unzip xdelta3; do
  31. exepath=`command -v "$appname" | tee`
  32. [ ! -e "$exepath" ] && fatal \
  33. "Required tool not found!" \
  34. "Please install: ${appname}."
  35. done
  36. # ======== Download tool setup
  37. DOWNLOAD_PATH="../_update_gi_download"
  38. #DOWNLOAD_PATH="../download with spaces"
  39. DL_APP_ARGS_axel='-n 15'
  40. DL_APP_ARGS_aria2c='--no-conf -c'
  41. DL_APP_ARGS_fetch='--force-restart --no-mtime --retry --keep-output --restart'
  42. DL_APP_ARGS_wget='-c'
  43. DL_APP_ARGS_curl='--disable -C -'
  44. # Find first available download tool.
  45. for appname in axel aria2c fetch wget curl; do
  46. # Pipe to "tee" overwrites the exit status
  47. exepath=`command -v "$appname" | tee`
  48. if [ -e "$exepath" ]; then
  49. DL_APP_BIN="$exepath"
  50. eval DL_APP_ARGS="\$DL_APP_ARGS_${appname}"
  51. break
  52. fi
  53. done
  54. [ ! -e "$DL_APP_BIN" ] && fatal \
  55. "No downloader application found." \
  56. "Please install one of: ${DL_APPS_LIST}."
  57. echo "--- Using download application: ${DL_APP_BIN} ${DL_APP_ARGS}"
  58. # ======== Functions
  59. # MacOS and *BSD do not have md5sum: use md5 instead
  60. exepath=$(command -v md5 | tee)
  61. if [ -e "$exepath" ]; then
  62. md5check() {
  63. # 1 = Checksum, 2 = File
  64. md5 -q -c "$1" "$2" >/dev/null 2>&1
  65. }
  66. else
  67. md5check() {
  68. # 1 = Checksum, 2 = File
  69. local input=`echo "$1" | tr 'A-Z' 'a-z'`
  70. local filesum=`md5sum "$2" | cut -d ' ' -f1`
  71. if [ "$input" != "$filesum" ]; then
  72. echo "Mismatch!"
  73. exit 1
  74. fi
  75. }
  76. fi
  77. download_file() {
  78. local url="$1"
  79. local dst_path="$2"
  80. local md5="$3"
  81. local filename_args="${dst_path}/${url##*/}"
  82. local filename="${filename_args%%\?*}"
  83. local filename_completed="${filename}.completed"
  84. echo
  85. mkdir -p "$dst_path"
  86. if [ -f "$filename_completed" ]; then
  87. echo "--> Skipping: Already downloaded."
  88. else
  89. (cd "$dst_path" && "$DL_APP_BIN" $DL_APP_ARGS "$url")
  90. if [ -n "$md5" ]; then
  91. echo -n '--- Verifying MD5 checksum ... '
  92. md5check "$md5" "$filename"
  93. echo 'OK'
  94. touch "$filename_completed"
  95. fi
  96. fi
  97. }
  98. # Approximate size of the installed archive
  99. download_json_size() {
  100. local size=`echo "$1" | jq -r -M ".size"`
  101. size="$((($size + 1048576) / 1024 / 1024 / 2))"
  102. echo "~${size} MiB"
  103. }
  104. download_json_section() {
  105. local json_text="$1"
  106. local url=`echo "$json_text" | jq -r -M ".path"`
  107. local md5=`echo "$json_text" | jq -r -M ".md5"`
  108. download_file "$url" "$DOWNLOAD_PATH" "$md5"
  109. }
  110. get_ini_value() {
  111. local filename="${1}"
  112. local variable="${2}"
  113. grep "${variable}=" "${filename}" | tr -d '\r\n' | sed -e 's|.*=||g'
  114. }
  115. # ======== Path sanity checks
  116. # There is a good reason for this check. Do not pollute the game directory.
  117. [ -e "${THIS_PATH}/${ANCHOR_FILE}" ] && fatal \
  118. "Please move this script outside the game directory prior executing." \
  119. " -> See README.md for proper installation instructions"
  120. # In case people accidentally want to install the game into the launcher directory.
  121. [ `find ./*.dll 2>/dev/null | wc -l` -gt 2 ] && fatal \
  122. "This script is likely run in the wrong directory." \
  123. "Found more than two DLL files. (expected: 0...2)" \
  124. "Please run this script in a proper/clean game directory."
  125. # ======== Command line processing
  126. # some help
  127. if [ "$1" = '-h' -o "${1#--}" = 'help' ]; then
  128. cat << HELP
  129. ${THIS_FILE} [-h|help] [install]
  130. This script will modify the current working directory.
  131. Specify "install" for a new installation, or nothing to perform an update.
  132. See README.md for details and examples.
  133. HELP
  134. exit 0
  135. fi
  136. # Option to install.
  137. if [ "$1" = 'install' ]; then
  138. if [ `find ./* 2>/dev/null | wc -l` -gt 0 ]; then
  139. fatal "'${PWD}' contains files and/or subdirectories." \
  140. "Please use an empty directory for a new installation." \
  141. "To update or resume an installtion, rerun this script without the 'install' argument."
  142. fi
  143. echo -e '[General]\r\nchannel=1\r\ncps=mihoyo\r\ngame_version=0.0.0\r\nsub_channel=0\r\n' >"$CONFIG_FILE"
  144. fi
  145. # ======== Configuration file parsing
  146. [ ! -e "$CONFIG_FILE" ] && fatal \
  147. "Game information file ${CONFIG_FILE} not found." \
  148. "Make sure 'Genshin Impact Game' is the current working directory." \
  149. "If you would like to install the game append the 'install' option:" \
  150. "sh '${THIS_PATH}/${THIS_FILE}' install"
  151. installed_ver=`get_ini_value "${CONFIG_FILE}" 'game_version'`
  152. [ -z "$installed_ver" ] && fatal \
  153. "Cannot read game_version from ${CONFIG_FILE}. File corrupt?"
  154. echo "--- Installed version: ${installed_ver}"
  155. # ======== Update information download + meta checks
  156. # WARNING: File cannot be downloaded to NTFS/FAT* partitions due to special characters
  157. tmp_path=`mktemp -d`
  158. trap "rm -rf '$tmp_path'" EXIT
  159. download_file "$UPDATE_URL" "$tmp_path"
  160. RESOURCE_FILE=`find "$tmp_path" -name 'resource*' | tee`
  161. [ ! -f "${RESOURCE_FILE}" ] && fatal \
  162. "Failed to download version info. Check your internet connection."
  163. upstream_ver=`jq -r -M '.data .game .latest .version' "$RESOURCE_FILE" | tee`
  164. echo "--- Latest version: ${upstream_ver}"
  165. if [ "$upstream_ver" = "$installed_ver" ]; then
  166. echo
  167. echo "==> Client is up to date."
  168. exit 0
  169. fi
  170. # Check whether this version can be patched
  171. patcher_dir="$REPO_PATH"/`echo "$upstream_ver" | tr -d .`
  172. [ ! -d "$patcher_dir" ] && fatal \
  173. "No suitable patch script found. Please check the bug tracker for details about the progress."
  174. # ======== Select update type
  175. # Check is diff update possible.
  176. archive_json=`jq -r -M ".data .game .diffs[] | select(.version==\"${installed_ver}\")" "$RESOURCE_FILE"`
  177. if [ -z "$archive_json" ]; then
  178. # Fallback to full download.
  179. archive_json=`jq -r -M '.data .game .latest' "$RESOURCE_FILE"`
  180. dl_type="new installation"
  181. else
  182. dl_type="incremental update"
  183. fi
  184. size=`download_json_size "$archive_json"`
  185. echo "Download type: ${dl_type} (${size})"
  186. # Confirm install/update.
  187. while :; do
  188. read -r -p "Start/continue update? [Y/n]: " input
  189. #input="y"
  190. case "$input" in
  191. Y|y|'')
  192. echo
  193. break
  194. ;;
  195. n|N)
  196. exit 0
  197. ;;
  198. esac
  199. done
  200. # Download main game archive
  201. download_json_section "$archive_json"
  202. # ======== Locate and install voiceover packs
  203. if [ -d "$LANG_PACKS_PATH" ]; then
  204. # WARNING: Does not handle spaces. Do not add $PWD.
  205. lang_dir_names=`find "$LANG_PACKS_PATH"/* -type d | xargs basename | tr -d '()' | tr 'a-z' 'A-Z'`
  206. fi
  207. # Download langs packs.
  208. for dir_name in ${lang_dir_names}; do
  209. eval lang_code="\$LANG_MAP_${dir_name}"
  210. lang_archive_json=`echo ${archive_json} | jq -r -M ".voice_packs[] | select(.language==\"${lang_code}\")"`
  211. if [ "$lang_archive_json" = 'null' ]; then
  212. echo "Cannot find update for language: ${lang_code}"
  213. continue
  214. fi
  215. size=`download_json_size "$lang_archive_json"`
  216. echo "--- Voiceover pack: ${lang_code} (${size})"
  217. download_json_section "$lang_archive_json"
  218. done
  219. # ======== Revert patch & apply update
  220. # Run 'patch_revert.sh' on update existing installation.
  221. if [ -e "$ANCHOR_FILE" ]; then
  222. echo
  223. echo "============== Reverting previous Wine patch ==============="
  224. bash "${patcher_dir}/patch_revert.sh"
  225. echo "============================================================"
  226. echo
  227. fi
  228. # Unpack the game files and remove old ones according to deletefiles.txt
  229. echo "--- Updating game files...."
  230. find "${DOWNLOAD_PATH}" -name "*.zip" | while read archive_name; do
  231. # Prevent deleting of the possibly necessary files in case of full update.
  232. rm -f "deletefiles.txt"
  233. short_name=`basename "${archive_name}"`
  234. echo "--- Installing ${short_name} ... "
  235. unzip -o "${archive_name}"
  236. rm -f "${archive_name}" "${archive_name}.completed"
  237. # Process new deletefiles
  238. [ ! -f "deletefiles.txt" ] && continue;
  239. while read -r line_full; do
  240. filename=`echo "${line_full}" | tr -d '\r\n'`
  241. # Safety check. File must be within the game directory.
  242. [ ! -f "${filename}" ] && continue;
  243. filepath_abs=`readlink -nf "${PWD}/${filename}"`
  244. case "$filepath_abs" in
  245. ("$PWD"/*)
  246. echo "--- Removing old file: ${filename}"
  247. rm -f "$filepath_abs"
  248. continue
  249. ;;
  250. esac
  251. echo "--- Attempt to remove: ${filename}"
  252. done < "deletefiles.txt"
  253. done
  254. rm -f "deletefiles.txt"
  255. # ======== Config update and game patching
  256. # Update version in config file.
  257. sed -i "s/game_version=${installed_ver}/game_version=${upstream_ver}/" "$CONFIG_FILE"
  258. # The directory should be empty now. Remove.
  259. rm -r "$DOWNLOAD_PATH"
  260. echo
  261. echo "==> Update to version ${upstream_ver} completed"
  262. # Run wine compatibility patch script.
  263. echo
  264. echo "================= Applying new Wine patch =================="
  265. bash "${patcher_dir}/patch.sh"
  266. echo "============================================================"
  267. echo "==> Update script completed successfully"
  268. exit 0