123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- #!/usr/bin/env bash
- # Checker: shellcheck --shell=bash --exclude=SC2006,SC3043 update_gi.sh | less
- # Exit on error.
- set -e
- fatal() {
- echo
- for arg in "$@"; do
- echo " * $arg" >&2
- done
- echo
- exit 1
- }
- # ======== Global constants
- DEBUG=${DEBUG:-0}
- declare -A UPDATE_URL_MAP
- UPDATE_URL_MAP[OS]='https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?channel_id=1&key=gcStgarh&launcher_id=10'
- UPDATE_URL_MAP[CN]='https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/resource?channel_id=1&key=eYd89JmJ&launcher_id=18'
- UPDATE_URL_MAP[BB]='https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/resource?channel_id=14&key=KAtdSsoQ&launcher_id=17'
- [ "$DEBUG" -eq 1 ] && UPDATE_URL_MAP[OS]='http://127.0.0.1:8000/resource.json'
- CONFIG_FILE='config.ini'
- ANCHOR_FILE='UnityPlayer.dll'
- # ======== Voice pack constants
- declare -A LANG_PACKS_PATH_MAP LANG_MAP
- LANG_PACKS_PATH_MAP[OS]='GenshinImpact_Data/StreamingAssets/AudioAssets'
- LANG_PACKS_PATH_MAP[CN]='YuanShen_Data/StreamingAssets/AudioAssets'
- LANG_PACKS_PATH_MAP[BB]=${LANG_PACKS_PATH_MAP[CN]}
- LANG_MAP[ENGLISHUS]='en-us'
- LANG_MAP[JAPANESE]='ja-jp'
- LANG_MAP[KOREAN]='ko-kr'
- LANG_MAP[CHINESE]='zh-cn'
- # ======== Evaluated variables
- THIS_FILE=`basename "$0"`
- THIS_PATH=`realpath "$(dirname "$0")"`
- REPO_PATH=`dirname "$THIS_PATH"` # parent
- reltype="" # OS, CN or BB. filled later.
- remove_archives=1 # 0 or 1. filled later.
- # ======== Dependency checks
- # Check is all required tools installed.
- for appname in jq bash 7za xdelta3; do
- exepath=`command -v "$appname" | tee`
- [ ! -e "$exepath" ] && fatal \
- "Required tool not found!" \
- "Please install: ${appname}."
- done
- # ======== Download tool setup
- DOWNLOAD_PATH="../_update_gi_download"
- #DOWNLOAD_PATH="../download with spaces"
- declare -A DL_APP_ARGS_MAP
- DL_APP_ARGS_MAP[axel]="-n 15"
- DL_APP_ARGS_MAP[aria2c]="--no-conf -c"
- DL_APP_ARGS_MAP[fetch]="--force-restart --no-mtime --retry --keep-output --restart"
- DL_APP_ARGS_MAP[wget]="-c"
- DL_APP_ARGS_MAP[curl]="--disable -O -C -"
- # Find first available download tool.
- for appname in axel aria2c fetch wget curl; do
- # Pipe to "tee" overwrites the exit status
- exepath=`command -v "$appname" | tee`
- if [ -e "$exepath" ]; then
- DL_APP_BIN="$exepath"
- read -ra DL_APP_ARGS <<< "${DL_APP_ARGS_MAP[$appname]}"
- break
- fi
- done
- [ ! -e "$DL_APP_BIN" ] && fatal \
- "No downloader application found." \
- "Please install one of: ${DL_APPS_LIST}."
- echo "--- Using download application: ${DL_APP_BIN}" "${DL_APP_ARGS[@]}"
- # ======== Functions
- # MacOS and *BSD do not have md5sum: use md5 instead
- exepath=$(command -v md5 | tee)
- if [ -e "$exepath" ]; then
- md5check() {
- # 1 = Checksum, 2 = File
- md5 -q -c "$1" "$2" >/dev/null 2>&1
- }
- else
- md5check() {
- local input filesum
- # 1 = Checksum, 2 = File
- input=`<<< "$1" tr '[:upper:]' '[:lower:]'`
- filesum=`md5sum "$2" | cut -d ' ' -f1`
- if [ "$input" != "$filesum" ]; then
- echo "Mismatch!"
- exit 1
- fi
- }
- fi
- download_file() {
- local url="$1"
- local dst_path="$2"
- local md5="$3"
- local filename_args="${dst_path}/${url##*/}"
- local filename="${filename_args%%\?*}"
- local filename_completed="${filename}.completed"
- mkdir -p "$dst_path"
- if [ -f "$filename_completed" ]; then
- echo " -> Skipping: Already downloaded."
- else
- echo
- (cd "$dst_path" && "$DL_APP_BIN" "${DL_APP_ARGS[@]}" "$url")
- if [ -n "$md5" ]; then
- echo -n " -> Verifying MD5 checksum ... "
- md5check "$md5" "$filename"
- echo 'OK'
- touch "$filename_completed"
- fi
- fi
- }
- # Approximate size of the installed archive
- download_json_size() {
- local size
- size=`<<< "$1" jq -r -M ".size"`
- echo "~$(((size + 1048576) / 1024 / 1024 / 2)) MiB"
- }
- # 1: { "path": "URL", "md5": "hash" }
- download_json_section() {
- local json_text
- local url md5
- json_text="$1"
- url=`<<< "$json_text" jq -r -M ".path"`
- md5=`<<< "$json_text" jq -r -M ".md5"`
- download_file "$url" "$DOWNLOAD_PATH" "$md5"
- }
- get_ini_value() {
- local filename variable
- filename="$1"
- variable="$2"
- grep "${variable}=" "${filename}" | tr -d '\r\n' | sed -e 's|.*=||g'
- }
- # ======== Path sanity checks
- # There is a good reason for this check. Do not pollute the game directory.
- [ -e "${THIS_PATH}/${ANCHOR_FILE}" ] && fatal \
- "Please move this script outside the game directory prior executing." \
- " -> See README.md for proper installation instructions"
- # In case people accidentally want to install the game into the launcher directory.
- dllcount=`find ./*.dll 2>/dev/null | wc -l`
- [ "$dllcount" -gt 2 ] && fatal \
- "This script is likely run in the wrong directory." \
- "Found more than two DLL files. (expected: 0...2)" \
- "Please run this script in a proper/clean game directory."
- # ======== At Exit cleanups
- tmp_path="" # json download path
- do_remove_config_ini=0 # when the install fails
- atexit() {
- if [[ -n "$tmp_path" && -d "$tmp_path" ]]; then
- rm -r "$tmp_path"
- fi
- if [ "$do_remove_config_ini" -gt 0 ]; then
- rm -f "$CONFIG_FILE"
- fi
- echo " -- exit cleanup done -- "
- }
- trap 'atexit' EXIT
- # ======== Command line processing
- cli_help=0
- cli_install=0
- cli_predownload=0
- json_path=".game"
- for arg in "$@"; do
- case "$arg" in
- -h|--help|help)
- cli_help=1
- ;;
- install)
- cli_install=1
- ;;
- nodelete)
- remove_archives=0
- echo "--- Archives will not be deleted after download"
- ;;
- predownload)
- cli_predownload=1
- json_path=".pre_download_game"
- echo "--- Checking for predownload versions"
- ;;
- *)
- fatal "Unknown option: ${arg}"
- esac
- done
- if [ "$cli_help" -eq 1 ]; then
- cat << HELP
- ${THIS_FILE} [-h|help] [install] [nodelete]
- This script will modify the current working directory.
- See README.md for details and examples.
- "install" : new game installation from scratch
- "nodelete" : whether to keep the downloaded archives
- "predownload" : Checks and downloads pre-download-game archives
- HELP
- exit 0
- fi
- # New game installation option
- if [ "$cli_install" -eq 1 ]; then
- dircount=`find ./* 2>/dev/null | wc -l`
- if [ "$dircount" -gt 0 ]; then
- fatal "'${PWD}' contains files and/or subdirectories." \
- "Please use an empty directory for a new installation." \
- "To update or resume an installation, rerun this script without the 'install' argument."
- fi
- do_remove_config_ini=1
- echo ""
- echo " 0 -> Genshin Impact [America/Europe/Asia/TW,HK,MO]"
- echo " 1 -> YuanShen [Mainland China]"
- echo " 2 -> BiliBili [Mainland China]"
- read -rp "Which version shall be installed? [0]/1/2: " choice
- if [[ -z "$choice" || "$choice" == "0" ]]; then
- reltype="OS"
- 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"
- elif [ "$choice" = "1" ]; then
- reltype="CN"
- 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"
- elif [ "$choice" = "2" ]; then
- reltype="BB"
- 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"
- else
- fatal "Invalid selection"
- fi
- else
- # Check for existing installation
- if [ -e "GenshinImpact.exe" ]; then
- reltype="OS"
- elif [ -e "YuanShen.exe" ]; then
- if [ -e "$DATADIR/Plugins/PCGameSDK.dll" ]; then
- reltype="BB"
- else
- reltype="CN"
- fi
- fi
- fi
- # ======== Configuration file parsing
- game_not_found_message=(
- "Make sure 'Genshin Impact Game' is the current working directory."
- "If you would like to install the game append the 'install' option:"
- "bash '${THIS_PATH}/${THIS_FILE}' install"
- )
- [ -z "$reltype" ] && fatal \
- "Cannot determine the installed game type." \
- "${game_not_found_message[@]}"
- LANG_PACKS_PATH=${LANG_PACKS_PATH_MAP[$reltype]}
- UPDATE_URL=${UPDATE_URL_MAP[$reltype]}
- unset reltype
- [ ! -e "$CONFIG_FILE" ] && fatal \
- "Game information file ${CONFIG_FILE} not found." \
- "${game_not_found_message[@]}"
- installed_ver=`get_ini_value "${CONFIG_FILE}" 'game_version'`
- [ -z "$installed_ver" ] && fatal \
- "Cannot read game_version from ${CONFIG_FILE}. File corrupt?"
- echo "--- Installed version: ${installed_ver}"
- # ======== Update information download + meta checks
- # WARNING: File cannot be downloaded to NTFS/FAT* partitions due to special characters
- tmp_path=`mktemp -d`
- download_file "$UPDATE_URL" "$tmp_path"
- RESOURCE_FILE=`find "$tmp_path" -name 'resource*' | tee`
- [ ! -f "${RESOURCE_FILE}" ] && fatal \
- "Failed to download version info. Check your internet connection."
- upstream_ver=`jq -r -M ".data ${json_path} .latest .version" "$RESOURCE_FILE" | tee`
- [ "$upstream_ver" = "null" ] && fatal "Could not find any matching update entry"
- echo "--- Latest version: ${upstream_ver}"
- if [ "$upstream_ver" = "$installed_ver" ]; then
- echo
- echo "==> Client is up to date."
- exit 0
- fi
- if [[ "$cli_predownload" -eq 0 && "$DEBUG" -eq 0 ]]; then
- # Check whether this version can be patched
- patcher_dir="$REPO_PATH"/`<<< "$upstream_ver" tr -d .`
- [ ! -d "$patcher_dir" ] && fatal \
- "No suitable patch script found. Please check the bug tracker for details about the progress."
- fi
- # ======== Select update type
- # Check is diff update possible.
- archive_json=`jq -r -M ".data ${json_path} .diffs[] | select(.version==\"${installed_ver}\")" "$RESOURCE_FILE"`
- if [ -z "$archive_json" ]; then
- # Fallback to full download.
- archive_json=`jq -r -M ".data ${json_path} .latest" "$RESOURCE_FILE"`
- dl_type="new installation"
- [ "$cli_install" -eq 0 ] && fatal "Cannot find an update for ${installed_ver} -> ${upstream_ver}." \
- "Please use a new directory to install the game from scatch."
- else
- dl_type="incremental update"
- fi
- size=`download_json_size "$archive_json"`
- echo "Download type: ${dl_type} (${size})"
- if [ "$cli_install" -eq 1 ]; then
- updated_ver="$upstream_ver"
- else
- # The version after updating might differ from the newest available version (bad package selection?)
- updated_ver=$(<<< "$archive_json" jq -r -M ".name")
- [[ "$updated_ver" =~ \.[0-9]+\.[0-9]+_([0-9]+\.[0-9]+\.[0-9]+)_ ]];
- updated_ver=${BASH_REMATCH[1]}
- if [ "$upstream_ver" != "$updated_ver" ]; then
- echo -e "=!= WARNING: Cannot find a direct update for ${installed_ver} -> ${upstream_ver}.\n" \
- " This script will first update to version ${updated_ver}.\n" \
- " Please re-run the update script after completing this update."
- fi
- fi
- # Confirm install/update.
- while :; do
- if [ "$DEBUG" -eq 0 ]; then
- read -rp "Start/continue update? [Y]/n: " input
- else
- input="y"
- fi
- case "$input" in
- Y|y|'')
- echo
- break
- ;;
- n|N)
- exit 0
- ;;
- esac
- done
- echo "--- Main game archive"
- # Download SDK if exists
- sdk_json=$(jq -r -M ".data .sdk | select(.!=null)" "$RESOURCE_FILE")
- if [ -n "$sdk_json" ]; then
- download_json_section "$sdk_json"
- fi
- # Get all segments (array) or empty string
- main_archive_segments=$(<<< "$archive_json" jq -r -M ".segments | select(.!=null)")
- # Download the main game or update archive(s)
- if [ -n "$main_archive_segments" ]; then
- i=0
- while :; do
- section=$(<<< "$main_archive_segments" jq -r -M ".[${i}] | select(.!=null)")
- [ -z "$section" ] && break
- download_json_section "$section"
- i=$((i + 1))
- done
- else
- download_json_section "$archive_json"
- fi
- # ======== Locate and install voiceover packs
- lang_dir_names() {
- if [ -d "$LANG_PACKS_PATH" ]; then
- # Get voiceover directory name in capitals. Does proper space handling.
- find "$LANG_PACKS_PATH" -mindepth 1 -type d -print0 \
- | xargs -0 -L1 -r basename \
- | tr -d '()' \
- | tr '[:lower:]' '[:upper:]'
- fi
- }
- lang_dir_names_str=$(lang_dir_names)
- if [[ -z "$lang_dir_names_str" && "$cli_install" -eq 1 ]]; then
- # TODO: make the default language(s) selectable
- lang_dir_names_str="ENGLISHUS"
- fi
- # Download langs packs.
- while read -r dir_name; do
- [ -z "$dir_name" ] && continue
- lang_code=${LANG_MAP[$dir_name]}
- lang_archive_json=`<<< "${archive_json}" jq -r -M ".voice_packs[] | select(.language==\"${lang_code}\")"`
- if [ "$lang_archive_json" = 'null' ] || [ "$lang_archive_json" = '' ]; then
- echo "--- Cannot find update for language: ${dir_name}"
- continue
- fi
- size=`download_json_size "$lang_archive_json"`
- echo "--- Voiceover pack: ${lang_code} (${size})"
- download_json_section "$lang_archive_json"
- done <<< "$lang_dir_names_str"
- # ======== Revert patch & apply update
- if [ "$cli_predownload" -eq 1 ]; then
- echo
- echo "==> Pre-download completed. The archives will be ready on release day."
- exit 0
- fi
- # Run 'patch_revert.sh' on update existing installation.
- if [ -e "$ANCHOR_FILE" ]; then
- echo
- echo "============== Reverting previous Wine patch ==============="
- [ "$DEBUG" -eq 0 ] && bash "${patcher_dir}/patch_revert.sh"
- echo "============================================================"
- echo
- fi
- # Unpack the game files and remove old ones according to deletefiles.txt
- echo "--- Updating game files ..."
- # -L to allow symlinking the download directory to another drive
- zip_files=$(find -L "$DOWNLOAD_PATH" \( -name "*.zip" -o -name "*.zip.001" \))
- while read -r archive_name; do
- [ ! -f "${archive_name}.completed" ] && fatal \
- "Archive '${archive_name}' is not marked as complete!"
- bash "${THIS_PATH}/perform_update.sh" "$archive_name"
- done <<< "$zip_files"
- if [ "$remove_archives" -eq 1 ]; then
- # Remove downloads when all updates succeeded
- # otherwise keep the archives to fix with "perform_update.sh" manually
- echo "--- Removing downloaded archives ..."
- while read -r archive_name; do
- rm -f "${archive_name}" "${archive_name}.completed"
- done <<< "$zip_files"
- fi
- # ======== Config update and game patching
- do_remove_config_ini=0
- # Update version in config file.
- sed -i "s/game_version=${installed_ver}/game_version=${updated_ver}/" "$CONFIG_FILE"
- if [ -n "$sdk_json" ]; then
- sdk_version=`<<< "$sdk_json" jq -r -M ".version"`
- sed -i "s/^sdk_version=.*/sdk_version=${sdk_version}/" "$CONFIG_FILE"
- fi
- if [ "$remove_archives" -eq 1 ]; then
- # The directory should be empty now. Remove.
- rm -r "$DOWNLOAD_PATH"
- fi
- echo
- echo "==> Update to version ${updated_ver} completed"
- # Run wine compatibility patch script.
- echo
- echo "================= Applying new Wine patch =================="
- [ "$DEBUG" -eq 0 ] && bash "${patcher_dir}/patch.sh"
- echo "============================================================"
- echo "==> Update script completed successfully"
- exit 0
|