update_gi.sh 9.1 KB

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