Expand local_machine docs and automation for connectivity, games, and ops.

Add proxy/VPN/telegram launchd and emergency runbooks; reorganize apps docs;
document JA3 CrossOver runbook and Wine troubleshooting; add GOG/HoMM game
scripts, disk cleanup guides, and gitea push-via-proxy helper. Ignore temp/.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ahauimix
2026-06-01 08:12:19 +03:00
parent a39ef89125
commit 1938b7c743
135 changed files with 9933 additions and 388 deletions

View File

@@ -0,0 +1,211 @@
#!/bin/bash
# Push local_machine to Gitea on server 206.189.35.205 via Proxy (🇷🇺 ДЛЯ РФ)
# Same logic as hunabapp's script: SSH → HTTPS → Bundle
# Repo: local_machine, branch: main, target: 206.189.35.205
#
# === БЕЗОПАСНОСТЬ (SAFETY) ===
# - НЕ трогает текущую ветку, HEAD, рабочую копию, локальные коммиты.
# - НЕ выполняет: git reset, git checkout --hard, git clean, push --force.
# - Обновляет refs/remotes/origin/main только после успешного пуша.
# - --dry-run: только показать, что будет запушено.
set -e
DRY_RUN=0
[[ "${1:-}" = "--dry-run" ]] && DRY_RUN=1
if [[ "$DRY_RUN" -eq 1 ]]; then
BRANCH="${2:-main}"
else
BRANCH="${1:-main}"
fi
# Target: Gitea on 206.189.35.205 (mirror server)
# Optional: in ~/.ssh/config set Host gitea-mirror, HostName 206.189.35.205, User <your_user>
# Then: export GITEA_SERVER=gitea-mirror
GITEA_SSH_HOST="${GITEA_SSH_HOST:-206.189.35.205}"
GITEA_SSH_PORT="${GITEA_SSH_PORT:-2223}"
GITEA_SSH_URL="ssh://git@${GITEA_SSH_HOST}:${GITEA_SSH_PORT}/hunabgit/local_machine.git"
# Host for scp/ssh (docker exec on 206). Default: IP. Or set GITEA_SERVER=gitea-mirror if in SSH config.
SERVER="${GITEA_SERVER:-206.189.35.205}"
# HTTPS (if mirror has own URL, set GITEA_HTTPS_BASE)
GITEA_HTTPS_BASE="${GITEA_HTTPS_BASE:-https://gitea.hunab.app/hunabgit/local_machine.git}"
# Proxy (🇷🇺 ДЛЯ РФ)
PROXY_SERVER="${PROXY_SERVER:-hsites-ahau}"
PROXY_SSH_KEY="${PROXY_SSH_KEY:-$HOME/.ssh/id_ed25519}"
TARGET_SSH_KEY="${TARGET_SSH_KEY:-$HOME/.ssh/hunab_deploy_key}"
PROXY_SSH_KEY_EXPANDED="${PROXY_SSH_KEY/#\~/$HOME}"
TARGET_SSH_KEY_EXPANDED="${TARGET_SSH_KEY/#\~/$HOME}"
PROXY_CMD="ssh -i $PROXY_SSH_KEY_EXPANDED -o StrictHostKeyChecking=accept-new -W %h:%p $PROXY_SERVER"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}=== Push local_machine to Gitea via Proxy (206.189.35.205) ===${NC}"
echo -e "Branch: ${YELLOW}$BRANCH${NC}"
echo -e "Proxy: ${YELLOW}$PROXY_SERVER${NC}"
echo -e "Target: ${YELLOW}$GITEA_SSH_HOST${NC} (repo: local_machine)"
echo ""
# Must be in local_machine repo
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || true)
if [[ ! -f "$REPO_ROOT/.gitignore" ]] || [[ ! -d "$REPO_ROOT/docs" ]]; then
echo -e "${RED}❌ Run from local_machine repo root.${NC}"
exit 1
fi
if ! git rev-parse --verify "origin/$BRANCH" >/dev/null 2>&1; then
echo -e "${YELLOW}⚠️ origin/$BRANCH not found, fetching...${NC}"
git fetch origin 2>/dev/null || true
fi
LOCAL_COMMIT=$(git rev-parse $BRANCH 2>/dev/null || echo "")
REMOTE_COMMIT=$(git rev-parse "origin/$BRANCH" 2>/dev/null || echo "")
if [ -z "$LOCAL_COMMIT" ]; then
echo -e "${RED}❌ Error: Branch $BRANCH not found${NC}"
exit 1
fi
if [ -z "$REMOTE_COMMIT" ]; then
echo -e "${YELLOW}⚠️ Remote branch not found, will create it${NC}"
REMOTE_COMMIT=""
elif [ "$LOCAL_COMMIT" = "$REMOTE_COMMIT" ]; then
echo -e "${GREEN}✅ No commits to push${NC}"
exit 0
fi
echo -e "Local commit: ${BLUE}$LOCAL_COMMIT${NC}"
if [ -n "$REMOTE_COMMIT" ]; then
echo -e "Remote commit: ${BLUE}$REMOTE_COMMIT${NC}"
fi
PENDING_COUNT=$(git rev-list --count "origin/$BRANCH..$BRANCH" 2>/dev/null || echo "?")
echo -e "Commits to push: ${BLUE}$PENDING_COUNT${NC}"
echo ""
if [[ "$DRY_RUN" -eq 1 ]]; then
echo -e "${GREEN}✅ --dry-run: would push $PENDING_COUNT commit(s). No changes made.${NC}"
echo " Run without --dry-run to push: bash $0 $BRANCH"
exit 0
fi
# Method 1: SSH via proxy
echo -e "${YELLOW}[1/3] Trying SSH push...${NC}"
CURRENT_URL=$(git remote get-url origin 2>/dev/null || echo "")
if ssh -p "$GITEA_SSH_PORT" -i "$TARGET_SSH_KEY_EXPANDED" -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new -o ProxyCommand="$PROXY_CMD" "git@$GITEA_SSH_HOST" "echo 'OK'" >/dev/null 2>&1; then
echo -e "${GREEN}✅ SSH connection available${NC}"
if [ -z "$CURRENT_URL" ] || [[ ! "$CURRENT_URL" =~ ssh:// ]]; then
echo -e "${YELLOW}Switching to SSH URL...${NC}"
git remote set-url origin "$GITEA_SSH_URL"
fi
GIT_SSH_COMMAND="ssh -i $TARGET_SSH_KEY_EXPANDED -p $GITEA_SSH_PORT -o ProxyCommand='$PROXY_CMD' -o StrictHostKeyChecking=accept-new"
if GIT_SSH_COMMAND="$GIT_SSH_COMMAND" git push origin "$BRANCH" 2>&1; then
echo -e "${GREEN}✅ Push successful via SSH!${NC}"
exit 0
else
echo -e "${YELLOW}⚠️ SSH push failed, trying HTTPS...${NC}"
fi
else
echo -e "${YELLOW}⚠️ SSH not available, trying HTTPS...${NC}"
fi
# Method 2: HTTPS
echo -e "${YELLOW}[2/3] Trying HTTPS push...${NC}"
if [ -z "$CURRENT_URL" ] || [[ "$CURRENT_URL" =~ ssh:// ]]; then
git remote set-url origin "$GITEA_HTTPS_BASE"
fi
git config http.postBuffer 524288000
git config http.lowSpeedLimit 0
git config http.lowSpeedTime 0
git config http.timeout 600
if command -v timeout >/dev/null 2>&1; then
if timeout 30 git push origin "$BRANCH" 2>&1; then
echo -e "${GREEN}✅ Push successful via HTTPS!${NC}"
exit 0
fi
else
echo -e "${YELLOW}⚠️ Skipping HTTPS (no timeout cmd), using bundle...${NC}"
fi
# Method 3: Bundle
echo -e "${YELLOW}[3/3] Using bundle method...${NC}"
BUNDLE_BASE="$REMOTE_COMMIT"
if git remote get-url gitea >/dev/null 2>&1; then
GIT_SSH_COMMAND_FETCH="ssh -i $TARGET_SSH_KEY_EXPANDED -p $GITEA_SSH_PORT -o ProxyCommand='$PROXY_CMD' -o StrictHostKeyChecking=accept-new -o ConnectTimeout=15"
if GIT_SSH_COMMAND="$GIT_SSH_COMMAND_FETCH" git fetch gitea "$BRANCH" 2>/dev/null; then
GITEA_COMMIT=$(git rev-parse "gitea/$BRANCH" 2>/dev/null || echo "")
[ -n "$GITEA_COMMIT" ] && BUNDLE_BASE="$GITEA_COMMIT" && echo -e "Bundle base (gitea/$BRANCH): ${BLUE}$GITEA_COMMIT${NC}"
fi
fi
if [ "$BUNDLE_BASE" = "$REMOTE_COMMIT" ]; then
echo -e "${YELLOW}Querying Gitea refs/heads/$BRANCH on server...${NC}"
GITEA_COMMIT=$(ssh -i "$TARGET_SSH_KEY_EXPANDED" -o ProxyCommand="$PROXY_CMD" "$SERVER" "docker exec gitea sh -c 'cd /data/git/repositories/hunabgit/local_machine.git && git rev-parse refs/heads/$BRANCH'" 2>/dev/null || echo "")
if [ -n "$GITEA_COMMIT" ]; then
BUNDLE_BASE="$GITEA_COMMIT"
echo -e "Bundle base (Gitea on server): ${BLUE}$GITEA_COMMIT${NC}"
else
echo -e "${YELLOW}⚠️ Could not get server ref (repo may be new). Using full branch bundle.${NC}"
fi
fi
mkdir -p "$REPO_ROOT/temp"
BUNDLE_FILE="$REPO_ROOT/temp/gitea-push-${BRANCH}-$(date +%s).bundle"
echo "Creating bundle: $BUNDLE_FILE"
if [ -n "$BUNDLE_BASE" ] && ! git cat-file -e "$BUNDLE_BASE^{commit}" 2>/dev/null; then
echo -e "${YELLOW}⚠️ Server commit not in local repo. Creating full branch bundle.${NC}"
BUNDLE_BASE=""
fi
BUNDLE_FAILED=0
if [ -n "$BUNDLE_BASE" ]; then
git bundle create "$BUNDLE_FILE" "$BUNDLE_BASE..$BRANCH" || BUNDLE_FAILED=1
else
git bundle create "$BUNDLE_FILE" "$BRANCH" || BUNDLE_FAILED=1
fi
if [ "$BUNDLE_FAILED" -eq 1 ]; then
echo -e "${RED}❌ Bundle creation failed.${NC}"
exit 1
fi
BUNDLE_SIZE=$(du -h "$BUNDLE_FILE" | cut -f1)
echo "Bundle size: $BUNDLE_SIZE"
echo "Copying bundle to server via proxy..."
scp -i "$TARGET_SSH_KEY_EXPANDED" -o ProxyCommand="$PROXY_CMD" "$BUNDLE_FILE" "$SERVER:/tmp/$(basename "$BUNDLE_FILE")" || {
echo -e "${RED}❌ Failed to copy bundle.${NC}"
echo -e " Check: GITEA_SERVER=$SERVER (e.g. 206.189.35.205 or Host from ~/.ssh/config), proxy $PROXY_SERVER reachable."
rm -f "$BUNDLE_FILE"
exit 1
}
NEW_COMMIT=$(git bundle list-heads "$BUNDLE_FILE" | grep "^$BRANCH" | cut -d" " -f1)
[ -z "$NEW_COMMIT" ] && NEW_COMMIT=$(git bundle list-heads "$BUNDLE_FILE" | head -1 | cut -d" " -f1)
if [ -z "$NEW_COMMIT" ]; then
echo -e "${RED}❌ Could not find commit in bundle${NC}"
rm -f "$BUNDLE_FILE"
exit 1
fi
BUNDLE_BASENAME=$(basename "$BUNDLE_FILE")
APPLY_OUT=$(mktemp 2>/dev/null || echo "/tmp/gitea-apply-$$.out")
if ! ssh -i "$TARGET_SSH_KEY_EXPANDED" -o ProxyCommand="$PROXY_CMD" "$SERVER" "docker cp /tmp/$BUNDLE_BASENAME gitea:/tmp/push-bundle.bundle && docker exec -e BRANCH='$BRANCH' -e NEW_COMMIT='$NEW_COMMIT' gitea sh -c 'cd /data/git/repositories/hunabgit/local_machine.git && git bundle unbundle /tmp/push-bundle.bundle && git update-ref refs/heads/\$BRANCH \$NEW_COMMIT && rm -f /tmp/push-bundle.bundle' && rm -f /tmp/$BUNDLE_BASENAME" 2>"$APPLY_OUT"; then
echo -e "${RED}❌ Failed to apply bundle${NC}"
[ -s "$APPLY_OUT" ] && cat "$APPLY_OUT"
echo -e "${YELLOW}→ Ensure repo exists on 206: Gitea UI → New Repository → local_machine (empty).${NC}"
echo " Then: git remote add gitea $GITEA_SSH_URL && git fetch gitea $BRANCH"
echo " And re-run: bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh $BRANCH"
rm -f "$APPLY_OUT" "$BUNDLE_FILE"
exit 1
fi
rm -f "$APPLY_OUT" "$BUNDLE_FILE"
if [ "$NEW_COMMIT" = "$LOCAL_COMMIT" ]; then
echo -e "${YELLOW}Updating local tracking ref (origin/$BRANCH)...${NC}"
git update-ref "refs/remotes/origin/$BRANCH" "$NEW_COMMIT" 2>/dev/null || true
fi
echo -e "${GREEN}✅ Push completed via bundle!${NC}"
echo -e " Commit: ${BLUE}$NEW_COMMIT${NC}"

0
scripts/games/README.md Normal file
View File

2
scripts/games/cache/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

214
scripts/games/config.sh Executable file
View File

@@ -0,0 +1,214 @@
# Shared paths for HoMM / Whisky (source from other scripts in this folder).
GAMES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GAMES_CACHE_DIR="${GAMES_DIR}/cache"
BOTTLE_ID="38412122-AAE1-4831-9F2E-6A0EB364B3F9"
BOTTLE_NAME="HoMM-OldenEra"
# franke Whisky 3 (Whisky.app) — канон; isaacmarovitz legacy после migrate-bottle-to-franke-whisky.sh
_FRANKE_BOTTLE="${HOME}/Library/Containers/com.franke.Whisky/Bottles/${BOTTLE_ID}"
_ISAAC_BOTTLE="${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles/${BOTTLE_ID}"
if [[ -d "${_FRANKE_BOTTLE}/drive_c" ]]; then
BOTTLE_DIR="${_FRANKE_BOTTLE}"
WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky"
else
BOTTLE_DIR="${_ISAAC_BOTTLE}"
WHISKY_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky"
fi
unset _FRANKE_BOTTLE _ISAAC_BOTTLE
WINE64="${WHISKY_LIBS}/Libraries/Wine/bin/wine64"
WINE_LIB="${WHISKY_LIBS}/Libraries/Wine/lib"
STEAM_DIR="${BOTTLE_DIR}/drive_c/Program Files (x86)/Steam"
STEAM_EXE="${STEAM_DIR}/Steam.exe"
STEAM_SETUP="${GAMES_CACHE_DIR}/SteamSetup.exe"
LIBRARIES_TAR="${GAMES_CACHE_DIR}/Libraries.tar.gz"
# GOG Galaxy 2.0 (полный offline setup ~290 MB; web stub GOG_Galaxy_2.0.exe — только загрузчик)
GOG_SETUP="${GAMES_CACHE_DIR}/setup_galaxy.exe"
GOG_SETUP_URL="${GOG_SETUP_URL:-}"
GOG_DIR_X86="${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy"
GOG_DIR_X64="${BOTTLE_DIR}/drive_c/Program Files/GOG Galaxy"
GOG_EXE="${GOG_DIR_X86}/GalaxyClient.exe"
# CEF (как Steam/Whisky #1220). Без -cef-in-process-gpu — меньше gpu-process CPU spin.
GOG_GALAXY_BASE_ARGS="${GOG_GALAXY_BASE_ARGS:-/runWithoutUpdating /deelevated}"
GOG_GALAXY_CEF_ARGS="${GOG_GALAXY_CEF_ARGS:--allosarches -cef-force-32bit -no-cef-sandbox -cef-disable-gpu}"
homm_gog_cef_args_line() {
printf '%s %s' "${GOG_GALAXY_BASE_ARGS}" "${GOG_GALAXY_CEF_ARGS}"
}
homm_gog_cef_args_array() {
local _IFS=$IFS
IFS=' '
# shellcheck disable=SC2206
local -a _a=(${GOG_GALAXY_BASE_ARGS} ${GOG_GALAXY_CEF_ARGS})
IFS=$_IFS
printf '%s\n' "${_a[@]}"
}
homm_gog_exe() {
if [[ -f "${GOG_EXE}" ]]; then
echo "${GOG_EXE}"
return 0
fi
local found
found="$(find "${BOTTLE_DIR}/drive_c/Program Files"* -maxdepth 3 -name 'GalaxyClient.exe' 2>/dev/null | head -1)"
if [[ -n "${found}" ]]; then
echo "${found}"
return 0
fi
return 1
}
homm_wine_gog_running() {
pgrep -f "${BOTTLE_ID}.*[Gg]alaxy[Cc]lient" >/dev/null 2>&1 \
|| pgrep -f 'GOG Galaxy.*GalaxyClient' >/dev/null 2>&1 \
|| pgrep -f 'GalaxyClient\.exe' >/dev/null 2>&1
}
export_wine_env_gog() {
export_wine_env
unset WINEDLLOVERRIDES 2>/dev/null || true
export WINEDEBUG="${WINEDEBUG:--all}"
}
# Heroes of Might and Magic: Olden Era (Steam)
HOMM_APP_ID="3105440"
HOMM_DIR="${STEAM_DIR}/steamapps/common/Heroes of Might and Magic Olden Era"
HOMM_EXE="${HOMM_DIR}/HeroesOldenEra.exe"
WINEPATH_BIN="${WHISKY_LIBS}/Libraries/Wine/bin/winepath"
# Запуск игры: по умолчанию виртуальный стол Wine (часто лечит «двигается, ЛКМ нет»).
# HOMM_USE_WINEVDESKTOP=0 — запуск напрямую без explorer /desktop (быстрый A/B тест).
# HOMM_LAUNCH_DIRECT=1 — legacy-переключатель прямого запуска (оставлен для совместимости).
# HOMM_VDESKTOP=WxH — размер виртуального стола.
#
# По умолчанию Unity принудительно в оконном режиме (-screen-fullscreen 0 …):
# это часто восстанавливает ЛКМ на macOS+Wine при багах полноэкранного режима.
# Выключить: HOMM_UNITY_WINDOWED=0
HOMM_VDESKTOP="${HOMM_VDESKTOP:-1920x1080}"
HOMM_USE_WINEVDESKTOP="${HOMM_USE_WINEVDESKTOP:-1}"
HOMM_LAUNCH_DIRECT="${HOMM_LAUNCH_DIRECT:-0}"
HOMM_UNITY_WINDOWED="${HOMM_UNITY_WINDOWED:-1}"
# Дополнительные аргументы Unity через пробел, напр.: "-popupwindow"
HOMM_UNITY_EXTRA="${HOMM_UNITY_EXTRA:-}"
# Steam CEF (steamwebhelper): без этих флагов в Whisky/Wine часто нет окна UI
# (cef_log: message_window.cc Class does not exist, webhelper в цикле перезапуска).
# Источник: Whisky #1220 — -allosarches -cef-force-32bit -cef-in-process-gpu -no-cef-sandbox
HOMM_STEAM_CEF_ARGS="${HOMM_STEAM_CEF_ARGS:--allosarches -cef-force-32bit -cef-in-process-gpu -no-cef-sandbox -cef-disable-gpu}"
# HOMM_STEAM_VDESKTOP=1 — запуск через explorer /desktop (если окно «теряется»)
HOMM_STEAM_VDESKTOP="${HOMM_STEAM_VDESKTOP:-0}"
# Диск: APFS Data volume. Guard: block < HOMM_DISK_MIN_GB (3), warn < HOMM_DISK_WARN_PCT (10).
homm_data_volume_path() {
if [[ -d /System/Volumes/Data ]]; then
echo /System/Volumes/Data
elif [[ -n "${HOME:-}" && -d "${HOME}" ]]; then
echo "${HOME}"
else
echo /
fi
}
homm_disk_free_kb() {
df -k "$(homm_data_volume_path)" | awk 'NR==2 {print $4}'
}
homm_disk_total_kb() {
df -k "$(homm_data_volume_path)" | awk 'NR==2 {print $2}'
}
homm_disk_used_pct() {
df -k "$(homm_data_volume_path)" | awk 'NR==2 {gsub(/%/,"",$5); print $5}'
}
homm_disk_free_pct() {
local free_kb total_kb
free_kb="$(homm_disk_free_kb)"
total_kb="$(homm_disk_total_kb)"
if [[ "${total_kb}" -gt 0 ]]; then
echo $(( free_kb * 100 / total_kb ))
else
echo 0
fi
}
export_wine_env() {
export WINEPREFIX="${BOTTLE_DIR}"
export WINEMSYNC=1
export WINEESYNC=1
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
}
# Steam UI: без DXVK; dwrite=n,b — классический фикс пустого текста VGUI (см. fix-homm-steam-ui.sh).
export_wine_env_steam() {
export_wine_env
unset DXVK_ASYNC DXVK_HUD 2>/dev/null || true
export WINEDLLOVERRIDES="dwrite=n,b"
}
homm_kill_mac_steam() {
pkill -9 -f 'Steam.AppBundle/Steam/Contents/MacOS/steam_osx' 2>/dev/null || true
pkill -9 -f 'Steam.AppBundle/Steam/Contents/MacOS/ipcserver' 2>/dev/null || true
pkill -9 -f 'Steam Helper.app/Contents/MacOS/Steam Helper' 2>/dev/null || true
}
homm_steam_cef_args() {
# shellcheck disable=SC2206
echo ${HOMM_STEAM_CEF_ARGS}
}
homm_steam_connection_log() {
echo "${STEAM_DIR}/logs/connection_log.txt"
}
# Последняя сессия в connection_log — Logged On?
homm_steam_logged_on() {
local log
log="$(homm_steam_connection_log)"
[[ -f "${log}" ]] || return 1
tail -25 "${log}" | grep -qE '\[Logged On'
}
# Steam.exe в bottle (Terminal start.sh или Whisky GUI — argv часто C:\…\Steam\steam.exe).
# В regex избегать \S в "\Steam" — иначе ложные совпадения; используйте [/\\] или .
homm_wine_steam_running() {
pgrep -f "${BOTTLE_ID}.*[Ss]team\\.exe" >/dev/null 2>&1 \
|| pgrep -f "Whisky/Bottles/${BOTTLE_ID}.*[Ss]team\\.exe" >/dev/null 2>&1 \
|| pgrep -f 'Steam[/\\]steam\.exe' >/dev/null 2>&1 \
|| pgrep -f 'steamwebhelper\.exe' >/dev/null 2>&1
}
# Logged On + живой клиент (для play/game и сейвов).
homm_steam_ready_for_game() {
homm_steam_logged_on || return 1
homm_wine_steam_running
}
# Нативный Steam.app на Mac мешает bottle (login / CM / ipc).
homm_mac_steam_running() {
pgrep -f 'Steam\.AppBundle/Steam/Contents/MacOS/steam_osx' >/dev/null 2>&1
}
homm_warn_mac_steam() {
if homm_mac_steam_running; then
echo "⚠️ Запущен нативный Steam на Mac (часто остаётся после удаления Steam.app из /Applications)."
echo " Папка: ~/Library/Application Support/Steam/Steam.AppBundle/"
echo " Остановить:"
echo " pkill -f 'Steam.AppBundle/Steam/Contents/MacOS/steam_osx'"
echo " pkill -f 'Steam.AppBundle/Steam/Contents/MacOS/ipcserver'"
echo " Затем: bash ${GAMES_DIR}/start.sh reset && bash ${GAMES_DIR}/start.sh steam-ui"
return 1
fi
return 0
}
# Olden Era (Unity DX11): Gcenx DXVK-macOS в system32 — см. install-dxvk-macos.sh
export_wine_env_game() {
export_wine_env
export WINEDLLOVERRIDES="d3d11,d3d10core=n,b"
export DXVK_ASYNC=1
export DXVK_HUD=0
# Меньше конфликтов fullscreen с macOS Stage Manager / края экрана
export SDL_VIDEO_MAC_FULLSCREEN_SPACES="${SDL_VIDEO_MAC_FULLSCREEN_SPACES:-0}"
}

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Снимок логов GOG login + краткий разбор (auth.gog.com/token, refreshToken).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
LOGDIR="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs"
OUT="${GAMES_CACHE_DIR}/gog-auth-diagnose-$(date +%Y%m%d-%H%M%S)"
mkdir -p "${OUT}"
for f in GalaxyClient.log cef.log ClientHelper-Authentication.log GalaxyClientService.log; do
[[ -f "${LOGDIR}/${f}" ]] && cp -f "${LOGDIR}/${f}" "${OUT}/"
done
{
echo "=== $(date) ==="
echo "Bottle: ${BOTTLE_NAME} (${BOTTLE_ID})"
df -h "$(homm_data_volume_path)" | awk 'NR==2'
echo ""
echo "=== proxy (macOS) ==="
scutil --proxy 2>/dev/null | head -12
echo ""
echo "=== GOG processes ==="
pgrep -fl 'GalaxyClient' 2>/dev/null || echo "(none)"
echo ""
echo "=== refreshToken в реестре ==="
export WINEPREFIX="${BOTTLE_DIR}"
"${WINE64}" reg query 'HKCU\Software\GOG.com\Galaxy' /v refreshToken 2>&1 || echo "нет refreshToken"
echo ""
echo "=== последние auth-события (GalaxyClient.log) ==="
grep -iE 'Authentication failed|auth\.gog\.com/token|refreshToken|on_login_success|timeout.*gog|API status' \
"${LOGDIR}/GalaxyClient.log" 2>/dev/null | tail -25
echo ""
echo "=== CEF login (cef.log) ==="
grep -iE 'ERROR|message from source|recaptcha|frame-ancestors' "${LOGDIR}/cef.log" 2>/dev/null | tail -15
echo ""
echo "=== Mac → auth.gog.com/token ==="
curl -sS -o /dev/null -w "http:%{http_code} total:%{time_total}s\n" --max-time 15 \
-X POST "https://auth.gog.com/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&client_id=x&code=x" 2>&1 || echo "curl FAIL"
} > "${OUT}/summary.txt"
echo "Архив: ${OUT}"
cat "${OUT}/summary.txt"

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)/gog"
exec bash "${GOG_DIR}/diagnose-session.sh" "$@"

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Диагностика «курсор двигается, клики не работают» для HoMM Olden Era в Whisky.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
LOG="${BOTTLE_DIR}/drive_c/users/${USER}/AppData/LocalLow/Unfrozen/HeroesOldenEra/Player.log"
LOG_PREV="${BOTTLE_DIR}/drive_c/users/${USER}/AppData/LocalLow/Unfrozen/HeroesOldenEra/Player-prev.log"
echo "Bottle: ${BOTTLE_NAME}"
echo "Player.log: ${LOG}"
echo ""
if [[ ! -f "${LOG}" ]]; then
echo "Нет Player.log — хотя бы один раз запустите игру."
exit 1
fi
if grep -q 'EnableMouseInPointer failed' "${LOG}" 2>/dev/null; then
echo "ДИАГНОЗ: Unity + Wine — заглушка EnableMouseInPointer (WM_POINTER)."
echo "Строка из лога:"
grep -n 'EnableMouseInPointer' "${LOG}" | head -5 || true
echo ""
echo "WineBus / виртуальный desktop / SDL это НЕ лечат."
echo ""
echo "Рабочие пути:"
echo " 1) CrossOver (macOS) + при необходимости патч:"
echo " https://github.com/dabielf/crossover-unity-mouse-fix"
echo " 2) Ждать WhiskyWine/Wine с upstream-фиксом (см. Wine bug #53847)."
echo ""
DOCS="$(cd "${GAMES_DIR}/../.." && pwd)/docs/games/UNITY_POINTER_MOUSE_FIX.md"
echo "Подробности: ${DOCS}"
exit 0
fi
echo "Строка EnableMouseInPointer в текущем Player.log не найдена."
if [[ -f "${LOG_PREV}" ]] && grep -q 'EnableMouseInPointer failed' "${LOG_PREV}" 2>/dev/null; then
echo "(В Player-prev.log она была — перезапустите игру и снова этот скрипт.)"
fi
echo "Если клики всё равно мертвы — пришлите первые 80 строк Player.log в поддержку."

View File

@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Сейвы 1/2/3: файлы на диске vs видимость в игре (Steam API / login).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
CONN_LOG="${STEAM_DIR}/logs/connection_log.txt"
PLAYER_LOG="${BOTTLE_DIR}/drive_c/users/${USER}/AppData/LocalLow/Unfrozen/HeroesOldenEra/Player.log"
USERS_ROOT="${BOTTLE_DIR}/drive_c/users/${USER}/AppData/LocalLow/Unfrozen/HeroesOldenEra/users"
echo "=== HoMM Olden Era — диагностика сейвов ==="
echo "Bottle: ${BOTTLE_NAME}"
echo ""
# --- файлы на диске ---
if [[ -d "${USERS_ROOT}" ]]; then
echo "Профили users/:"
ls -1 "${USERS_ROOT}" 2>/dev/null || true
sp="$(find "${USERS_ROOT}" -type d -path '*/saves/singleplayer' 2>/dev/null | head -1)"
if [[ -n "${sp}" ]]; then
echo ""
echo "singleplayer на диске: ${sp}/"
ls -lh "${sp}"/*.saveskirmish 2>/dev/null || echo " (нет *.saveskirmish)"
else
echo "singleplayer: не найден"
fi
else
echo "Нет ${USERS_ROOT} — игра ещё не создавала профиль."
fi
echo ""
# --- Steam login ---
if [[ -f "${CONN_LOG}" ]]; then
echo "Steam connection_log (хвост):"
tail -4 "${CONN_LOG}" | sed 's/^/ /'
if tail -20 "${CONN_LOG}" | grep -qE '\[Logged On'; then
echo " → Steam в bottle: Logged On (OK для сейвов)"
STEAM_OK=1
else
echo " → Steam в bottle: Logged Off (сейвы в меню не подтянутся)"
STEAM_OK=0
fi
else
echo "Нет connection_log.txt"
STEAM_OK=0
fi
echo ""
# --- Player.log ---
if [[ -f "${PLAYER_LOG}" ]]; then
if grep -q 'SteamApi_Init failed\|ConnectToGlobalUser failed' "${PLAYER_LOG}" 2>/dev/null; then
echo "Player.log: SteamApi_Init FAILED — игра без живого Steam-клиента."
echo " (файлы на диске могут быть, слоты в UI пустые)"
API_OK=0
elif grep -q 'Steam is probably not running' "${PLAYER_LOG}" 2>/dev/null; then
echo "Player.log: Steam is probably not running"
API_OK=0
else
echo "Player.log: явных ошибок Steam API в последнем запуске нет."
API_OK=1
fi
if grep -q 'FileManager.*saves' "${PLAYER_LOG}" 2>/dev/null; then
echo " FileManager видит папку saves (путь профиля на диске совпадает)."
fi
else
echo "Нет Player.log — сначала запустите игру."
API_OK=0
fi
echo ""
if [[ "${STEAM_OK:-0}" == "1" && "${API_OK:-0}" == "1" ]]; then
echo "ВЕРДИКТ: контур сейвов выглядит здоровым. В игре: Загрузить → skirmish → слоты 1/2/3."
exit 0
fi
echo "ВЕРДИКТ: игра могла открыться, но сейвы не подтянутся, пока Steam не Logged On."
echo ""
echo "Цикл:"
echo " bash ${SCRIPT_DIR}/start.sh reset"
echo " bash ${SCRIPT_DIR}/fix-homm-steam-ui.sh # один раз, если пустой UI Steam"
echo " bash ${SCRIPT_DIR}/start.sh steam-ui # login в аккаунт"
echo " bash ${SCRIPT_DIR}/start.sh play # или Play в библиотеке"
echo ""
echo "Не использовать для сейвов: start.sh game без Logged On Steam."
exit 1

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Почему нет окна Wine-Steam после start.sh steam-ui.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
CEF_LOG="${STEAM_DIR}/logs/cef_log.txt"
CONSOLE_LOG="${STEAM_DIR}/logs/console_log.txt"
echo "=== Wine-Steam UI — диагностика ==="
echo "Bottle: ${BOTTLE_NAME} (${BOTTLE_ID})"
echo ""
echo "1) Mac Steam (мешает login / CEF):"
if homm_mac_steam_running; then
echo " ❌ Запущен steam_osx — killall steam_osx ipcserver"
else
echo " ✅ Mac Steam не мешает"
fi
echo ""
echo "2) Процессы bottle:"
if pgrep -f "${BOTTLE_ID}.*Steam\\.exe" >/dev/null 2>&1; then
echo " ✅ Steam.exe"
pgrep -lf "${BOTTLE_ID}.*Steam\\.exe" 2>/dev/null | head -2 | sed 's/^/ /'
else
echo " ❌ Steam.exe не запущен — bash ${SCRIPT_DIR}/start.sh steam-ui"
fi
if pgrep -f "${BOTTLE_ID}.*steamwebhelper" >/dev/null 2>&1 || pgrep -f "Whisky/Bottles/${BOTTLE_ID}.*steamwebhelper" >/dev/null 2>&1; then
echo " ✅ steamwebhelper (CEF UI)"
pgrep -lf 'steamwebhelper' 2>/dev/null | grep -E "${BOTTLE_ID}|Program Files \(x86\)\\\\Steam" | head -2 | sed 's/^/ /' || pgrep -lf steamwebhelper | head -1 | sed 's/^/ /'
else
echo " ❌ steamwebhelper нет — окно Steam (login) не появится"
echo " Типично: cef_log → message_window.cc Class does not exist"
fi
echo ""
echo "3) CEF-лог (последние ошибки):"
if [[ -f "${CEF_LOG}" ]]; then
if tail -40 "${CEF_LOG}" | grep -q 'message_window.cc'; then
echo " ❌ message_window.cc — steamwebhelper падает / перезапускается"
tail -40 "${CEF_LOG}" | grep 'message_window\|chrome_elf\|LdrInitialize' | tail -3 | sed 's/^/ /'
echo " Фикс: bash ${SCRIPT_DIR}/start.sh reset && bash ${SCRIPT_DIR}/start.sh steam-ui"
echo " (в start.sh уже: ${HOMM_STEAM_CEF_ARGS})"
else
echo " ✅ Нет свежих message_window в хвосте cef_log"
fi
else
echo " (cef_log.txt ещё нет — Steam не стартовал)"
fi
echo ""
echo "4) VGUI / шрифты (console_log):"
if [[ -f "${CONSOLE_LOG}" ]] && tail -30 "${CONSOLE_LOG}" | grep -q winFont; then
echo " ⚠️ winFont — пустой текст на кнопках VGUI"
echo " bash ${SCRIPT_DIR}/start.sh fix-steam-ui && bash ${SCRIPT_DIR}/start.sh reset && bash ${SCRIPT_DIR}/start.sh steam-ui"
else
echo " ✅ winFont в хвосте нет (или лог пуст)"
fi
echo ""
echo "5) Login:"
if homm_steam_logged_on; then
echo " ✅ Logged On"
else
echo " ❌ Logged Off — Retry / Enter Offline Mode в окне Steam"
fi
echo ""
echo "Где искать окно:"
echo " • Whisky.app → bottle HoMM-OldenEra (после migrate-bottle-to-franke-whisky.sh)"
echo " • Cmd+Tab (wine64), Mission Control"
echo " • Не HOMM_STEAM_VDESKTOP=1 — explorer: Failed to set primary display settings"

75
scripts/games/fix-gog-auth.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# GOG Galaxy в Wine: логин в CEF ок, но auth.gog.com/token таймаут → сброс на пароль.
# Фиксы: IPv6 в Wine, сеть Poco/OpenSSL, CEF-флаги GOG.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
cmd_wine_check() {
if [[ ! -x "${WINE64}" ]]; then
echo "Нет WhiskyWine: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh"
exit 1
fi
}
cmd_wine_network_registry() {
export WINEPREFIX="${BOTTLE_DIR}"
echo "Wine: отключаю IPv6 DNS (частая причина зависания WinHTTP/Poco)…"
"${WINE64}" reg add 'HKCU\Software\Wine\Network' /v DnsIPv6Enabled /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true
"${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \
/v DisableIPv6 /t REG_DWORD /d 1 /f >/dev/null 2>&1 || true
echo "Wine: ProxyEnable=0 (без системного прокси в bottle)…"
"${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \
/v ProxyEnable /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true
}
cmd_dll_overrides() {
export WINEPREFIX="${BOTTLE_DIR}"
local key='HKCU\Software\Wine\DllOverrides'
echo "Wine: DllOverrides для OpenSSL/Poco (native)…"
for dll in libssl-1_1 libcrypto-1_1 libssl-3 libcrypto-3; do
"${WINE64}" reg add "${key}" /v "${dll}" /t REG_SZ /d native,builtin /f >/dev/null 2>&1 || true
done
}
cmd_clear_gog_webcache() {
local cache="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/webcache"
if [[ -d "${cache}" ]]; then
rm -rf "${cache:?}"/*
echo "Очищен webcache: ${cache}"
fi
}
cmd_update_plist() {
local ps="${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist"
local args
args="$(homm_gog_cef_args_line)"
if [[ -f "${ps}" ]]; then
/usr/bin/plutil -replace arguments -string "${args}" "${ps}" 2>/dev/null \
|| echo " (plist вручную: ${args})"
echo "Обновлён ${ps}"
fi
}
main() {
cmd_wine_check
if ! homm_gog_exe >/dev/null; then
echo "GOG не установлен: bash ${SCRIPT_DIR}/start.sh gog-install"
exit 1
fi
cmd_wine_network_registry
cmd_dll_overrides
cmd_clear_gog_webcache
cmd_update_plist
echo ""
echo "OK. Перезапуск GOG:"
echo " bash ${SCRIPT_DIR}/start.sh gog-restart"
echo ""
echo "Если снова сброс на пароль — соберите логи:"
echo " bash ${SCRIPT_DIR}/diagnose-gog-auth.sh"
echo ""
echo "Проверка в логе после входа: НЕ должно быть «timeout … auth.gog.com/token»."
}
main "$@"

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Steam в Wine/Whisky: пустой VGUI (winFont), кнопки без текста → DWriteEnable + шрифты.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
cmd_wine_check() {
if [[ ! -x "${WINE64}" ]]; then
echo "Нет WhiskyWine: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh"
exit 1
fi
}
cmd_install_fonts() {
local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts"
local steam="${STEAM_DIR}"
mkdir -p "${fonts}"
for f in /System/Library/Fonts/Supplemental/{Arial,Tahoma,Verdana,"Times New Roman","Courier New"}.ttf; do
[[ -f "${f}" ]] && cp -n "${f}" "${fonts}/" 2>/dev/null || true
done
cp -n "${steam}/resource/fonts/marlett.ttf" "${fonts}/" 2>/dev/null || true
cp -n "${steam}/clientui/fonts/"*.ttf "${fonts}/" 2>/dev/null || true
echo "Шрифты в ${fonts}/ ($(ls "${fonts}" | wc -l | tr -d ' ') файлов)"
}
cmd_dwrite_registry() {
export WINEPREFIX="${BOTTLE_DIR}"
"${WINE64}" reg add 'HKCU\Software\Valve\Steam' /v DWriteEnable /t REG_DWORD /d 0 /f >/dev/null
echo "Registry: HKCU\\Software\\Valve\\Steam\\DWriteEnable = 0"
}
main() {
cmd_wine_check
cmd_install_fonts
cmd_dwrite_registry
echo ""
echo "OK. Перезапустите Steam:"
echo " bash ${SCRIPT_DIR}/start.sh reset"
echo " bash ${SCRIPT_DIR}/start.sh steam-ui"
echo ""
echo "Если текст всё ещё пустой — пройдите диалог вслепую: правая кнопка внизу или Tab → Enter."
}
main "$@"

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
MODE="${1:-sdl-on}"
export_wine_env
case "$MODE" in
sdl-on)
"${WINE64}" reg add 'HKLM\System\CurrentControlSet\Services\winebus' /v DisableHidraw /t REG_DWORD /d 1 /f >/dev/null
"${WINE64}" reg add 'HKLM\System\CurrentControlSet\Services\winebus' /v 'Enable SDL' /t REG_DWORD /d 1 /f >/dev/null
echo "WineBus SDL ON"
;;
sdl-off)
"${WINE64}" reg add 'HKLM\System\CurrentControlSet\Services\winebus' /v DisableHidraw /t REG_DWORD /d 0 /f >/dev/null
"${WINE64}" reg add 'HKLM\System\CurrentControlSet\Services\winebus' /v 'Enable SDL' /t REG_DWORD /d 0 /f >/dev/null
echo "WineBus SDL OFF"
;;
show)
"${WINE64}" reg query 'HKLM\System\CurrentControlSet\Services\winebus' /v DisableHidraw || true
"${WINE64}" reg query 'HKLM\System\CurrentControlSet\Services\winebus' /v 'Enable SDL' || true
;;
*)
echo "Usage: $(basename "$0") [sdl-on|sdl-off|show]"; exit 1
;;
esac

1
scripts/games/gog/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
bottle.env

View File

@@ -0,0 +1,64 @@
# GOG web-installer (без Steam)
Отдельный Whisky-bottle. Скрипты **не** трогают HoMM-OldenEra, Steam, `start.sh play`, дампы Steam.
Антипаттерны (swap, winedbg, «20 GB»): [docs/games/TROUBLESHOOTING.md](../../../docs/games/TROUBLESHOOTING.md#антипаттерны-не-делать).
## Один раз
```bash
# 1) Whisky → New Bottle → Windows 10 64-bit → имя «GOG-Install»
bash ~/code/local_machine/scripts/games/gog/list-bottles.sh
bash ~/code/local_machine/scripts/games/gog/use-bottle.sh <UUID> GOG-Install
# 2) Подготовка bottle (или одной командой: bash gog/install-all.sh /path/stub.exe)
bash ~/code/local_machine/scripts/games/gog/create-bottle.sh GOG-Install # если bottle ещё нет
bash ~/code/local_machine/scripts/games/gog/setup-bottle.sh
bash ~/code/local_machine/scripts/games/gog/install-gdiplus.sh # обязательно (~900MB скачивание)
brew install cabextract
bash ~/code/local_machine/scripts/games/gog/install-corefonts.sh
```
## Запуск web-stub
```bash
bash ~/code/local_machine/scripts/games/gog/run-web-installer.sh \
~/path/to/GOG_Galaxy_Jagged_Alliance_3.exe
```
Лог: `/tmp/gog-web-installer-GOG-Install.log`
## Jagged Alliance 3 **без GOG Galaxy** (канон)
**Runbook (установка + графика + RCA + антипаттерны):** [JA3_RUNBOOK.md](../../../docs/games/JA3_RUNBOOK.md)
```bash
# Установка (Whisky GOG-Install): 5 частей offline backup в ~/Games/
bash ~/code/local_machine/scripts/games/gog/install-ja3-innoextract.sh
bash ~/code/local_machine/scripts/games/gog/finish-ja3-layout.sh
# Запуск (CrossOver D3DMetal — один раз setup, затем каждый раз run):
brew install --cask crossover
bash ~/code/local_machine/scripts/games/gog/setup-ja3-crossover.sh
bash ~/code/local_machine/scripts/games/gog/run-ja3-crossover.sh
```
| Скрипт | Назначение |
|--------|------------|
| `install-ja3-innoextract.sh` | **Канон установки** (Whisky) |
| `finish-ja3-layout.sh` | CommonLua, реестр GOG |
| `setup-ja3-crossover.sh` | CrossOver bottle JA3-GOG + clone игры |
| **`run-ja3-crossover.sh`** | **Канон запуска** (D3D12 / D3DMetal) |
| `run-ja3.sh` | Whisky — **графика D3D12 не работает** (см. runbook) |
Galaxy / web-stub — **не использовать** для JA3 ([GOG_GALAXY_7H_RCA.md](../../../docs/games/GOG_GALAXY_7H_RCA.md)).
## Сброс только GOG
```bash
bash ~/code/local_machine/scripts/games/gog/reset.sh
```
## Устаревшее (не использовать для GOG)
- `setup-wine-bottle.sh` / bottle **HoMM-OldenEra** — там Steam и общий префикс.

View File

@@ -0,0 +1,6 @@
# Скопируйте в bottle.env и укажите отдельный Whisky-bottle (без Steam/HoMM).
# Создайте в Whisky: New Bottle → Windows 10 64-bit → имя «GOG-Install»
# UUID: Whisky → bottle → правый клик / About, или: bash list-bottles.sh
BOTTLE_ID=00000000-0000-0000-0000-000000000000
BOTTLE_NAME=GOG-Install

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Собрать GOG/Whisky логи за N часов (по умолчанию 7) + timeline событий.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
HOURS="${1:-7}"
OUT="${2:-${HOME}/code/local_machine/temp/gog-logs-${HOURS}h-$(date +%Y%m%d-%H%M%S)}"
GALOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs"
mkdir -p "${OUT}"
cp -R "${GALOG}" "${OUT}/galaxy-logs" 2>/dev/null || true
mkdir -p "${OUT}/whisky-logs"
cp "${HOME}/Library/Logs/com.franke.Whisky"/*.log "${OUT}/whisky-logs/" 2>/dev/null || true
[[ -f "/tmp/gog-galaxy-client-${BOTTLE_NAME}.log" ]] && cp "/tmp/gog-galaxy-client-${BOTTLE_NAME}.log" "${OUT}/" || true
df -h "$(gog_data_volume_path)" >"${OUT}/disk.txt"
pgrep -fl 'Galaxy|GOG|explorer.*GOG' >"${OUT}/processes-now.txt" 2>/dev/null || echo "(none)" >"${OUT}/processes-now.txt"
plutil -p "${BOTTLE_DIR}/Metadata.plist" >"${OUT}/metadata.plist.txt" 2>/dev/null || true
[[ -f "${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist" ]] && \
plutil -p "${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist" >"${OUT}/GalaxyClient.exe.plist.txt"
python3 <<PY
import re
from datetime import datetime, timedelta
from pathlib import Path
hours = int("${HOURS}")
out = Path("${OUT}")
cutoff = datetime.now() - timedelta(hours=hours)
pat = re.compile(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})")
keys = (
"will continue", "will exit", "Second client", "Showing login", "foreground",
"Deelevate", "Fatal", "deelevated", "Reloaded without", "Command-line parameters",
"parent window", "eglInitialize",
)
logdir = Path("${GALOG}")
lines = [f"# GOG Galaxy timeline (last {hours}h)\n", f"cutoff_local={cutoff.isoformat()}\n", f"bottle={Path('${BOTTLE_DIR}').name}\n\n"]
for fn in sorted(logdir.glob("*.log")):
lines.append(f"## {fn.name}\n")
for line in fn.read_text(errors="replace").splitlines():
m = pat.match(line)
if not m:
continue
try:
ts = datetime.strptime(m.group(1), "%Y-%m-%d %H:%M:%S.%f")
except ValueError:
continue
if ts < cutoff:
continue
if any(k.lower() in line.lower() for k in keys):
lines.append(line[:600] + "\n")
lines.append("\n")
(out / "timeline.md").write_text("".join(lines), encoding="utf-8")
PY
cat >"${OUT}/README.txt" <<EOF
GOG log bundle (${HOURS}h)
Generated: $(date)
Bottle: ${BOTTLE_NAME} (${BOTTLE_ID})
Read first: timeline.md
Galaxy logs: galaxy-logs/
Whisky: whisky-logs/
EOF
echo "OK: ${OUT}"
echo " timeline.md — события за ${HOURS}ч"
ls -la "${OUT}"

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
OUT="${1:-${HOME}/code/local_machine/temp/gog-setup-rca-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "${OUT}"
{
echo "=== $(date) ==="
echo "Bottle: ${BOTTLE_NAME} ${BOTTLE_ID}"
echo ""
echo "=== disk ==="
df -h /System/Volumes/Data | tail -1
echo ""
echo "=== processes ==="
pgrep -fl 'Galaxy|GOG_Galaxy' 2>/dev/null || echo "(none)"
echo ""
echo "=== GalaxyClient ==="
find "${BOTTLE_DIR}/drive_c" -name 'GalaxyClient.exe' 2>/dev/null || true
} >"${OUT}/summary.txt"
for f in \
"/tmp/gog-web-installer-${BOTTLE_NAME}.log" \
"/tmp/gog-galaxy-setup-${BOTTLE_NAME}.log"; do
[[ -f "${f}" ]] && cp "${f}" "${OUT}/"
done
find "${BOTTLE_DIR}/drive_c" -name 'Setup Log*.txt' 2>/dev/null | while read -r f; do
cp "${f}" "${OUT}/$(basename "${f}" | tr ' #' '_')"
done
find "${BOTTLE_DIR}/drive_c/ProgramData/GOG.com" -name '*.log' 2>/dev/null | while read -r f; do
cp "${f}" "${OUT}/$(basename "${f}")"
done
echo "${OUT}"

238
scripts/games/gog/config.sh Executable file
View File

@@ -0,0 +1,238 @@
# GOG-only Wine/Whisky — без Steam, HoMM, export_wine_env_steam.
GOG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
GAMES_CACHE_DIR="${GAMES_DIR}/cache"
_env_file="${GOG_DIR}/bottle.env"
if [[ -f "${_env_file}" ]]; then
# shellcheck source=/dev/null
source "${_env_file}"
elif [[ -n "${GOG_BOTTLE_ID:-}" ]]; then
BOTTLE_ID="${GOG_BOTTLE_ID}"
BOTTLE_NAME="${GOG_BOTTLE_NAME:-GOG-Install}"
else
echo "❌ Нет bottle для GOG."
echo " 1) Whisky → New Bottle (Windows 10 64-bit), имя GOG-Install"
echo " 2) bash ${GOG_DIR}/list-bottles.sh"
echo " 3) bash ${GOG_DIR}/use-bottle.sh <UUID>"
echo ""
echo " Не используйте bottle HoMM-OldenEra — там Steam, дампы и winedbg."
exit 1
fi
unset _env_file
_FRANKE="${HOME}/Library/Containers/com.franke.Whisky/Bottles/${BOTTLE_ID}"
_ISAAC="${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles/${BOTTLE_ID}"
if [[ -d "${_FRANKE}/drive_c" ]]; then
BOTTLE_DIR="${_FRANKE}"
WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky"
elif [[ -d "${_ISAAC}/drive_c" ]]; then
BOTTLE_DIR="${_ISAAC}"
WHISKY_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky"
else
echo "❌ Bottle не найден: ${BOTTLE_NAME} (${BOTTLE_ID})"
echo " Проверьте UUID: bash ${GOG_DIR}/list-bottles.sh"
exit 1
fi
unset _FRANKE _ISAAC
WINE64="${WHISKY_LIBS}/Libraries/Wine/bin/wine64"
WINE_LIB="${WHISKY_LIBS}/Libraries/Wine/lib"
WINEPATH_BIN="${WHISKY_LIBS}/Libraries/Wine/bin/winepath"
gog_data_volume_path() {
if [[ -d /System/Volumes/Data ]]; then
echo /System/Volumes/Data
else
echo "${HOME}"
fi
}
gog_disk_free_kb() {
df -k "$(gog_data_volume_path)" | awk 'NR==2 {print $4}'
}
gog_disk_guard() {
local min_gb="${1:-5}"
local min_kb free_kb
min_kb=$((min_gb * 1024 * 1024))
free_kb="$(gog_disk_free_kb)"
if [[ "${free_kb}" -lt "${min_kb}" ]]; then
echo "❌ Мало места (<${min_gb} GB на Data)."
return 1
fi
return 0
}
gog_swap_used_mb() {
local used
used="$(sysctl vm.swapusage 2>/dev/null | sed -n 's/.*used = \([0-9.]*\)M.*/\1/p')"
[[ -n "${used}" ]] || return 1
printf '%.0f' "${used}"
}
gog_swap_guard() {
local max_mb="${1:-400}"
local used
used="$(gog_swap_used_mb)" || return 0
if awk -v u="${used}" -v m="${max_mb}" 'BEGIN{exit !(u>=m)}'; then
echo "❌ Swap уже ~${used}MB (лимит ${max_mb}MB) — запуск GOG/Wine запрещён."
echo " bash ${GOG_DIR}/emergency-stop.sh"
echo " затем перезагрузка Mac (swap macOS отдаёт медленно)"
return 1
fi
return 0
}
# JA3/CrossOver: жёсткий стоп только если swap высокий И Wine/JA3 ещё живы.
# «Залипший» swap после закрытия (macOS не отдал) — предупреждение, запуск разрешён.
gog_wine_or_ja3_active() {
pgrep -f 'JA3\.exe|wineserver|wine64-preloader|winedbg|wine64\.exe|CrossOver.*wine' >/dev/null 2>&1
}
gog_swap_guard_ja3_launch() {
local soft_mb="${1:-1200}"
local hard_mb="${2:-4096}"
local used
used="$(gog_swap_used_mb)" || return 0
if [[ "${JA3_IGNORE_SWAP:-0}" == "1" ]]; then
echo "⚠️ JA3_IGNORE_SWAP=1 — swap ~${used}MB"
return 0
fi
if gog_wine_or_ja3_active; then
if awk -v u="${used}" -v m="${soft_mb}" 'BEGIN{exit !(u>=m)}'; then
echo "❌ Swap ~${used}MB и Wine/JA3 ещё работают (лимит ${soft_mb}MB)."
echo " bash ${GOG_DIR}/reset-ja3-crossover.sh или bash ${GOG_DIR}/emergency-stop.sh"
echo " затем reboot Mac. Или: JA3_IGNORE_SWAP=1 bash …/run-ja3-crossover.sh"
return 1
fi
return 0
fi
if awk -v u="${used}" -v m="${hard_mb}" 'BEGIN{exit !(u>=m)}'; then
echo "❌ Swap ~${used}MB (лимит ${hard_mb}MB) — reboot Mac."
echo " Или: JA3_IGNORE_SWAP=1 bash …/run-ja3-crossover.sh"
return 1
fi
if awk -v u="${used}" -v m="${soft_mb}" 'BEGIN{exit !(u>=m)}'; then
echo "⚠️ Swap ~${used}MB — залип после прошлых сессий (Wine не запущен). Запуск JA3…"
echo " (reboot снизит swap; JA3_IGNORE_SWAP=1 — принудительный игнор)"
fi
return 0
}
# Виртуальный стол Wine — окно CEF часто «есть в логах», но не видно на macOS без /desktop.
GOG_VDESKTOP="${GOG_VDESKTOP:-1280x800}"
# 0 = прямой GalaxyClient (рекомендуется; vdesktop — в Whisky Metadata или launch-galaxy-ui.sh).
GOG_USE_WINEVDESKTOP="${GOG_USE_WINEVDESKTOP:-0}"
# GOG_LAUNCH_FG=1 — exec в текущем терминале (лучше для видимости окна, блокирует shell).
GOG_LAUNCH_FG="${GOG_LAUNCH_FG:-0}"
# GOG_CEF_PROFILE=steam — добавить -cef-in-process-gpu (A/B если login пустой).
# /deelevated — обязательно в Wine (иначе DeelevateStrategy + exit -31, см. WineHQ #41584).
GOG_GALAXY_BASE_ARGS="${GOG_GALAXY_BASE_ARGS:-/runWithoutUpdating /deelevated}"
GOG_GALAXY_CEF_ARGS="${GOG_GALAXY_CEF_ARGS:--allosarches -cef-force-32bit -no-cef-sandbox -cef-disable-gpu}"
gog_galaxy_cef_args_array() {
local -a a=(${GOG_GALAXY_BASE_ARGS} ${GOG_GALAXY_CEF_ARGS})
if [[ "${GOG_CEF_PROFILE:-}" == "steam" ]]; then
a+=(-cef-in-process-gpu)
fi
printf '%s\n' "${a[@]}"
}
export_wine_env_gog() {
export WINEPREFIX="${BOTTLE_DIR}"
export WINEMSYNC=1
unset WINEESYNC 2>/dev/null || true
local _wine_ext="${WHISKY_LIBS}/Libraries/Wine/lib/external"
if [[ -d "${_wine_ext}/D3DMetal.framework" ]]; then
export DYLD_FRAMEWORK_PATH="${_wine_ext}:${DYLD_FRAMEWORK_PATH:-}"
export DYLD_LIBRARY_PATH="${_wine_ext}:${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
else
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
fi
unset _wine_ext
export WINEDLLOVERRIDES="gdiplus=n,b"
unset DXVK_ASYNC DXVK_HUD 2>/dev/null || true
export WINEDEBUG=-all
export MVK_CONFIG_LOG_LEVEL=0
}
ja3_gptk_available() {
[[ -d "${WHISKY_LIBS}/Libraries/Wine/lib/external/D3DMetal.framework" ]] && return 0
[[ -f "${WHISKY_LIBS}/Libraries/.gptk_installed" ]] && return 0
[[ -d "${WHISKY_LIBS}/D3DMetal" ]] && return 0
[[ -d "${WHISKY_LIBS}/GPTK" ]] && return 0
return 1
}
ja3_bottle_uses_d3dmetal() {
local meta="${BOTTLE_DIR}/Metadata.plist"
[[ -f "${meta}" ]] || return 1
plutil -extract graphicsConfig.backend raw -o - "${meta}" 2>/dev/null | grep -q d3dMetal
}
ja3_dxmt_installed() {
[[ -f "${BOTTLE_DIR}/.ja3_dxmt_installed" ]]
}
# JA3 = D3D12 (Porting Kit / GPTK). Без GPTK — best-effort D3D11+DXVK (d3d12=d).
export_wine_env_ja3() {
export_wine_env_gog
if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then
export WINED3DMETAL=1
unset DXVK_ASYNC DXVK_HUD DXVK_CONFIG_FILE 2>/dev/null || true
# builtin d3d12/dxgi + libd3dshared.so; без n,b на GPTK PE
export WINEDLLOVERRIDES="libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b"
return 0
fi
if ja3_dxmt_installed; then
# DXMT builtin: не ставить dxgi/d3d11=n,b (ломает загрузку, c000007b).
export WINEDLLOVERRIDES="d3d12=d;libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b"
else
export WINEDLLOVERRIDES="d3d11,d3d10core=n,b;d3d12=d;libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b"
fi
unset DXVK_ASYNC DXVK_HUD DXVK_CONFIG_FILE 2>/dev/null || true
export SDL_VIDEO_MAC_FULLSCREEN_SPACES="${SDL_VIDEO_MAC_FULLSCREEN_SPACES:-0}"
export MVK_CONFIG_RESUME_LOST_DEVICE=1
}
export_wine_env_ja3_d3dmetal() {
export_wine_env_gog
export WINED3DMETAL=1
export WINEDLLOVERRIDES="libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b"
unset DXVK_ASYNC DXVK_HUD 2>/dev/null || true
}
gog_steam_client_in_bottle() {
local f
[[ -d "${BOTTLE_DIR}/drive_c/Program Files (x86)/Steam" ]] && return 0
[[ -f "${BOTTLE_DIR}/drive_c/Program Files (x86)/Steam/steam.exe" ]] && return 0
f="$(find "${BOTTLE_DIR}/drive_c" -maxdepth 4 -iname 'steam.exe' -print -quit 2>/dev/null || true)"
[[ -n "${f}" ]]
}
gog_assert_no_steam_client() {
if gog_steam_client_in_bottle; then
echo "В bottle ${BOTTLE_NAME} найден Steam client — контур GOG/JA3 без Steam."
echo " bash ${GOG_DIR}/purge-steam-from-bottle.sh"
return 1
fi
if [[ "${BOTTLE_ID}" == "38412122-AAE1-4831-9F2E-6A0EB364B3F9" ]]; then
echo "❌ Это bottle HoMM-OldenEra (Steam). JA3 только в GOG-Install:"
echo " bash ${GOG_DIR}/use-bottle.sh <UUID-GOG-Install>"
return 1
fi
return 0
}
gog_wine_check() {
if [[ ! -x "${WINE64}" ]]; then
echo "Нет Wine: bash ${GAMES_DIR}/install-whiskywine-manual.sh"
exit 1
fi
if [[ ! -d "${BOTTLE_DIR}/drive_c" ]]; then
echo "❌ Нет drive_c в ${BOTTLE_DIR}"
echo " Откройте bottle «${BOTTLE_NAME}» в Whisky один раз."
exit 1
fi
}

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# JA3 = DirectX 12. На Apple Silicon канон: Whisky + Game Porting Toolkit (D3DMetal), не DXVK.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
META="${BOTTLE_DIR}/Metadata.plist"
[[ -f "${META}" ]] || {
echo "❌ Нет ${META} — откройте bottle ${BOTTLE_NAME} в Whisky."
exit 1
}
if ! ja3_gptk_available; then
if [[ -d "/Applications/Game Porting Toolkit.app" ]]; then
bash "${GOG_DIR}/install-gptk-whisky.sh"
else
echo "❌ Game Porting Toolkit не установлен."
echo " brew install --cask gcenx/wine/game-porting-toolkit"
echo " bash ${GOG_DIR}/install-gptk-whisky.sh"
exit 1
fi
fi
/usr/libexec/PlistBuddy -c 'Set :graphicsConfig:backend d3dMetal' "${META}" 2>/dev/null || \
/usr/libexec/PlistBuddy -c 'Add :graphicsConfig:backend string d3dMetal' "${META}"
/usr/libexec/PlistBuddy -c 'Set :dxvkConfig:dxvk false' "${META}" 2>/dev/null || true
bash "${GOG_DIR}/force-ja3-d3d11.sh" >/dev/null 2>&1 || true
# С GPTK можно d3d12 — сбросить принудительный d3d11 в LocalStorage при желании:
# perl -i -pe 's/GraphicsApi = "d3d11"/GraphicsApi = "d3d12"/' ...
bash "${GOG_DIR}/prepare-ja3-d3dmetal.sh"
echo "OK: bottle ${BOTTLE_NAME} → Graphics Backend D3DMetal"
echo " Запуск: bash ${GOG_DIR}/run-ja3.sh"
echo " Whisky Run: C:\\GOG Games\\Jagged Alliance 3\\run-ja3.cmd (не JA3.exe — иначе graphics/CommonLua)"

View File

@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# Создать пустой Whisky-bottle «GOG-Install» (wineboot --init), без Steam.
set -euo pipefail
GOG_SCRIPTS="$(cd "$(dirname "$0")" && pwd)"
FRANKE="${HOME}/Library/Containers/com.franke.Whisky"
WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky/Libraries"
WINE64="${WHISKY_LIBS}/Wine/bin/wine64"
WINE_LIB="${WHISKY_LIBS}/Wine/lib"
NAME="${1:-GOG-Install}"
if [[ -f "${GOG_SCRIPTS}/bottle.env" ]]; then
# shellcheck source=/dev/null
source "${GOG_SCRIPTS}/bottle.env"
if [[ -d "${FRANKE}/Bottles/${BOTTLE_ID}/drive_c" ]]; then
echo "Bottle уже есть: ${BOTTLE_NAME} (${BOTTLE_ID})"
exit 0
fi
fi
if [[ ! -x "${WINE64}" ]]; then
echo "Нет Wine: bash $(dirname "$GOG_SCRIPTS")/install-whiskywine-manual.sh"
exit 1
fi
BOTTLE_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
BOTTLE_PATH="${FRANKE}/Bottles/${BOTTLE_ID}"
mkdir -p "${BOTTLE_PATH}"
echo "Создаю префикс Wine: ${BOTTLE_PATH} (25 мин)…"
export WINEPREFIX="${BOTTLE_PATH}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
export WINEDEBUG=-all
"${WINE64}" wineboot --init
python3 <<PY
import plistlib
from pathlib import Path
meta = Path("${BOTTLE_PATH}") / "Metadata.plist"
data = {
"info": {"name": "${NAME}", "pins": [], "blocklist": []},
"displayConfig": {
"customWidth": 1920,
"customHeight": 1080,
"resolutionPreset": "r1920x1080",
"virtualDesktopEnabled": False,
},
"dxvkConfig": {"dxvk": False, "dxvkAsync": True, "dxvkHud": {"off": {}}},
"graphicsConfig": {"backend": "recommended"},
"audioConfig": {
"audioDriver": "auto",
"latencyPreset": "defaultPreset",
"outputDeviceMode": "followSystem",
},
"cleanupConfig": {
"clipboardPolicy": "auto",
"clipboardThreshold": 10240,
"closeWithProcessesPolicy": "ask",
"killOnQuit": "inherit",
},
"customDLLOverrides": [],
"fileVersion": {"major": 1, "minor": 0, "patch": 0, "build": "", "preRelease": ""},
}
with meta.open("wb") as f:
plistlib.dump(data, f)
print("Metadata.plist OK")
PY
BOTTLE_VM="${FRANKE}/BottleVM.plist"
python3 <<PY
import plistlib
from pathlib import Path
vm = Path("${BOTTLE_VM}")
url = "file://${BOTTLE_PATH}"
if vm.exists():
with vm.open("rb") as f:
data = plistlib.load(f)
else:
data = {"fileVersion": {"major": 1, "minor": 0, "patch": 0, "build": "", "preRelease": ""}, "paths": []}
paths = data.setdefault("paths", [])
if not any(p.get("relative", "").endswith("${BOTTLE_ID}") for p in paths):
paths.append({"relative": url})
with vm.open("wb") as f:
plistlib.dump(data, f)
print("BottleVM.plist OK")
PY
if command -v whisky >/dev/null 2>&1; then
whisky add "${BOTTLE_PATH}" 2>/dev/null || true
fi
cat >"${GOG_SCRIPTS}/bottle.env" <<EOF
BOTTLE_ID=${BOTTLE_ID}
BOTTLE_NAME=${NAME}
EOF
echo ""
echo "OK: ${NAME}"
echo " UUID: ${BOTTLE_ID}"
echo " Path: ${BOTTLE_PATH}"
echo "Дальше: bash ${GOG_SCRIPTS}/setup-bottle.sh"

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# RCA: процесс жив, окна нет — логи + процессы (без Apple Events).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
OUT="${1:-${HOME}/code/local_machine/temp/gog-window-rca-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "${OUT}"
GALAXY_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/GalaxyClient.log"
INIT_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/GalaxyInitialization.log"
CEF_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/cef.log"
LAUNCH_LOG="/tmp/gog-galaxy-client-${BOTTLE_NAME}.log"
{
echo "=== GOG Galaxy window RCA ==="
date
echo "bottle: ${BOTTLE_NAME} ${BOTTLE_ID}"
echo "GOG_USE_WINEVDESKTOP=${GOG_USE_WINEVDESKTOP:-?} GOG_VDESKTOP=${GOG_VDESKTOP:-?}"
echo ""
echo "--- pgrep Galaxy ---"
pgrep -fl 'GalaxyClient|explorer.exe /desktop=GOG' 2>/dev/null || echo "(нет процессов)"
echo ""
echo "--- disk ---"
df -h "$(gog_data_volume_path)" 2>/dev/null | tail -1
} >"${OUT}/summary.txt"
for f in "${GALAXY_LOG}" "${INIT_LOG}" "${CEF_LOG}" "${LAUNCH_LOG}"; do
[[ -f "${f}" ]] || continue
base="$(basename "${f}")"
tail -80 "${f}" >"${OUT}/${base}.tail" 2>/dev/null || true
done
grep -E 'Second client|will continue|Showing login|foreground|Failed to locate|parent window' \
"${GALAXY_LOG}" "${CEF_LOG}" 2>/dev/null >"${OUT}/signals.txt" || true
echo "OK: ${OUT}"
echo " summary.txt, signals.txt, *.tail"
echo ""
echo "Если в signals.txt есть login/foreground, а окна нет:"
echo " bash ${GOG_DIR}/reset.sh"
echo " bash ${GOG_DIR}/launch-galaxy-ui.sh # vdesktop + foreground"
echo " Cmd+Tab → wine64 / GOGGalaxy / GalaxyClient"

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
RCA="${GAMES_DIR}/../temp/gog-rca-$(date +%Y%m%d-%H%M%S)"
LOG="/tmp/gog-web-installer-${BOTTLE_NAME}.log"
mkdir -p "${RCA}"
{
echo "GOG bottle: ${BOTTLE_NAME} (${BOTTLE_ID})"
df -h "$(gog_data_volume_path)"
sysctl vm.swapusage 2>/dev/null || true
echo ""
echo "markers:"; ls -la "${BOTTLE_DIR}"/.gog_* 2>/dev/null || true
echo ""
du -sh "${BOTTLE_DIR}" "${BOTTLE_DIR}/drive_c/windows/Fonts" 2>/dev/null || true
echo ""
echo "log ${LOG}:"
tail -30 "${LOG}" 2>/dev/null || echo "(нет)"
echo ""
echo "winedbg: $(pgrep winedbg 2>/dev/null | wc -l | tr -d ' ')"
pgrep -lf 'GOG_Galaxy|GalaxyWeb' 2>/dev/null || echo "(нет GOG процессов)"
} | tee "${RCA}/report.txt"
cp "${LOG}" "${RCA}/" 2>/dev/null || true
echo "RCA: ${RCA}"

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Снять нагрузку GOG/Wine + winedbg (swap). Безопасно для GOG-bottle.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=gog-process.sh
source "${GOG_DIR}/gog-process.sh"
_winedbg_count() {
pgrep winedbg 2>/dev/null | wc -l | tr -d ' '
}
echo "=== До ==="
sysctl vm.swapusage 2>/dev/null || true
echo "winedbg: $(_winedbg_count)"
gog_kill_all
pkill -9 winedbg 2>/dev/null || true
echo ""
echo "=== После kill ==="
pgrep -fl 'Galaxy|GOG Galaxy' 2>/dev/null || echo "Galaxy: (нет)"
echo "winedbg: $(_winedbg_count)"
sysctl vm.swapusage 2>/dev/null || true
echo ""
echo "Если Mac всё ещё тормозит — перезагрузка (swap отдаётся медленно)."
echo "Galaxy UI в Wine на этом Mac не подтверждён — JA3: docs/games/GOG_GALAXY_7H_RCA.md § Offline"

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Скачать только лаунчер (~1 MB) для multipart offline backup JA3.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
JA3_GAMES_DIR="${JA3_GAMES_DIR:-${HOME}/Games}"
OUT="${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730).exe"
mkdir -p "${JA3_GAMES_DIR}"
if [[ -f "${OUT}" ]]; then
echo "Уже есть: ${OUT}"
exit 0
fi
if ! command -v lgogdownloader >/dev/null 2>&1; then
echo "❌ Нужен lgogdownloader: brew install lgogdownloader"
exit 1
fi
echo "Скачивание en1installer0 (~1 MB) → ${OUT}"
echo "Если ошибка login — выполните: lgogdownloader --login"
echo ""
lgogdownloader \
--download-file "jagged_alliance_3/en1installer0" \
--output-file "$(basename "${OUT}")" \
--directory "${JA3_GAMES_DIR}"
if [[ -f "${OUT}" ]]; then
ls -lh "${OUT}"
echo "OK. Дальше: bash ${GOG_DIR}/install-ja3-multipart.sh"
else
# lgogdownloader может сохранить под другим именем
f="$(find "${JA3_GAMES_DIR}" -maxdepth 1 -iname 'setup*jagged*alliance*3*.exe' -print -quit 2>/dev/null || true)"
if [[ -n "${f}" ]]; then
echo "Скачан как: ${f}"
echo "Запуск: JA3_SETUP_EXE='${f}' bash ${GOG_DIR}/install-ja3-multipart.sh"
else
echo "❌ .exe не появился в ${JA3_GAMES_DIR}"
exit 1
fi
fi

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Устарело: Galaxy UI. Канон — install-ja3.sh (offline backup без Galaxy).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "Канон JA3 без Galaxy: bash ${GOG_DIR}/install-ja3.sh"
echo "Док: docs/games/JA3_MAC_WITHOUT_GALAXY.md"
exec bash "${GOG_DIR}/install-ja3.sh"

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Доп. шаги после innoextract: layout GOG, реестр, DXVK, CommonLua.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
JA3="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3"
[[ -f "${JA3}/JA3.exe" ]] || {
echo "❌ Нет JA3 — сначала install-ja3-innoextract.sh"
exit 1
}
# Inno Setup копирует CommonLua в корень; innoextract — только ModTools/ (Wine не видит symlink)
if [[ ! -f "${JA3}/CommonLua/Core/autorun.lua" ]]; then
rm -rf "${JA3}/CommonLua"
cp -R "${JA3}/ModTools/Src/CommonLua" "${JA3}/CommonLua"
echo " CommonLua скопирован из ModTools/Src (~9 MB)"
fi
bash "${GOG_DIR}/register-ja3-registry.sh"
bash "${GOG_DIR}/install-ja3-redist.sh" 2>/dev/null || true
if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then
bash "${GOG_DIR}/prepare-ja3-d3dmetal.sh" 2>/dev/null || bash "${GOG_DIR}/install-gptk-whisky.sh" || true
else
if [[ -d "/Applications/Game Porting Toolkit.app" ]]; then
bash "${GOG_DIR}/install-gptk-whisky.sh"
else
bash "${GOG_DIR}/install-ja3-dxmt.sh" 2>/dev/null || bash "${GOG_DIR}/install-dxvk.sh"
fi
fi
cp -f "${GOG_DIR}/ja3-dxvk.conf" "${JA3}/dxvk.conf" 2>/dev/null || true
if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then
perl -i -pe 's/^\t\tGraphicsApi = "d3d11"/\t\tGraphicsApi = "d3d12"/' \
"${BOTTLE_DIR}/drive_c/users/${WINE_USER:-eternal}/AppData/Roaming/Jagged Alliance 3/LocalStorage.lua" 2>/dev/null || true
else
bash "${GOG_DIR}/force-ja3-d3d11.sh"
fi
# Intel XeSS / DLSS DLL в папке игры ломают D3D11 на MoltenVK — убрать с пути загрузки
mkdir -p "${JA3}/.disabled-upscale"
for dll in libxess.dll nvngx_dlss.dll XeFX.dll XeFX_Loader.dll dxcompiler.dll dxil.dll; do
[[ -f "${JA3}/${dll}" ]] && mv -f "${JA3}/${dll}" "${JA3}/.disabled-upscale/${dll}"
done
bash "${GOG_DIR}/purge-steam-from-bottle.sh" >/dev/null 2>&1 || true
echo "OK: finish-ja3-layout"

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Курсор JA3 в CrossOver (Retina / HiDPI).
# Профили (JA3_MOUSE_PROFILE):
# legacy — RetinaMode=n (полный ход по Y; возможен сдвиг клика) — default
# retina-lite — RetinaMode=y без LogPixels (если legacy смещает, но нет клипа)
# retina — RetinaMode=y + LogPixels=192 (клип на середине экрана — не default)
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
CX="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver"
CX_BOTTLE="JA3-GOG"
CX_DIR="${HOME}/Library/Application Support/CrossOver/Bottles/${CX_BOTTLE}"
[[ -d "${CX}" ]] || {
echo "❌ CrossOver не установлен"
exit 1
}
PROFILE="${JA3_MOUSE_PROFILE:-legacy}"
WREG() { "${CX}/bin/wine" --bottle "${CX_BOTTLE}" "$@"; }
case "${PROFILE}" in
retina)
RETINA="y"
SET_DPI=1
DPI="${JA3_LOG_PIXELS:-192}"
SET_WIN8=1
;;
retina-lite)
RETINA="y"
SET_DPI=0
SET_WIN8=0
;;
legacy|*)
RETINA="n"
SET_DPI=0
SET_WIN8=0
PROFILE="legacy"
;;
esac
WREG reg add 'HKCU\Software\Wine\Mac Driver' /v RetinaMode /t REG_SZ /d "${RETINA}" /f >/dev/null
if [[ "${SET_DPI}" == "1" ]]; then
WREG reg add 'HKCU\Control Panel\Desktop' /v LogPixels /t REG_DWORD /d "${DPI}" /f >/dev/null
else
WREG reg delete 'HKCU\Control Panel\Desktop' /v LogPixels /f >/dev/null 2>&1 || true
fi
if [[ "${SET_WIN8}" == "1" ]]; then
WREG reg add 'HKCU\Control Panel\Desktop' /v Win8DpiScaling /t REG_DWORD /d 1 /f >/dev/null
else
WREG reg delete 'HKCU\Control Panel\Desktop' /v Win8DpiScaling /f >/dev/null 2>&1 || true
fi
CONF="${CX_DIR}/cxbottle.conf"
if [[ -f "${CONF}" ]] && ! grep -q 'SDL_VIDEO_MAC_FULLSCREEN_SPACES' "${CONF}"; then
sed -i '' '/^\[EnvironmentVariables\]/a\
"SDL_VIDEO_MAC_FULLSCREEN_SPACES" = "0"
' "${CONF}" 2>/dev/null || true
fi
echo "OK: profile=${PROFILE} RetinaMode=${RETINA}${CX_BOTTLE}"
echo " Перезапуск: bash ${GOG_DIR}/reset-ja3-crossover.sh && bash ${GOG_DIR}/run-ja3-crossover.sh"
case "${PROFILE}" in
legacy)
echo " Если клик смещён: JA3_MOUSE_PROFILE=retina-lite bash ${GOG_DIR}/fix-ja3-crossover-mouse.sh"
echo " Без перезапуска: верхний левый угол окна = угол экрана."
;;
retina-lite)
echo " Если смещение осталось: JA3_MOUSE_PROFILE=legacy …"
echo " Если клип на середине: JA3_MOUSE_PROFILE=legacy …"
;;
retina)
echo " ⚠️ retina+LogPixels=192 часто режет курсор на середине экрана."
echo " Если так: JA3_MOUSE_PROFILE=legacy bash ${GOG_DIR}/fix-ja3-crossover-mouse.sh"
;;
esac

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# JA3 на Mac (Whisky без GPTK): рендер через D3D11 + DXVK, не D3D12 (wine d3d12 → «Failed creating render device»).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
WINE_USER="${WINE_USER:-eternal}"
LS="${BOTTLE_DIR}/drive_c/users/${WINE_USER}/AppData/Roaming/Jagged Alliance 3/LocalStorage.lua"
if [[ ! -f "${LS}" ]]; then
mkdir -p "$(dirname "${LS}")"
cat >"${LS}" <<'EOF'
return {
Options = {
GraphicsApi = "d3d11",
VideoPreset = "Low",
},
}
EOF
echo "OK: создан ${LS} (GraphicsApi=d3d11)"
exit 0
fi
if grep -q 'GraphicsApi = "d3d11"' "${LS}"; then
echo "OK: GraphicsApi уже d3d11"
exit 0
fi
perl -i -pe '
if (/^\t\tGraphicsApi = /) {
$_ = "\t\tGraphicsApi = \"d3d11\",\n";
}
' "${LS}"
echo "OK: ${LS} → GraphicsApi=d3d11 (было d3d12 или пусто)"

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Общие kill/lock для GOG Galaxy в GOG-bottle.
GOG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
WINE_SERV="${WHISKY_LIBS}/Libraries/Wine/bin/wineserver"
gog_lock_files_dir() {
echo "${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/lock-files"
}
gog_remove_lock_files() {
local d
d="$(gog_lock_files_dir)"
[[ -d "${d}" ]] || return 0
rm -f "${d}"/*.lock 2>/dev/null || true
}
gog_kill_all() {
pkill -9 -f 'explorer.exe /desktop=GOGGalaxy' 2>/dev/null || true
pkill -9 -f 'explorer /desktop=GOGGalaxy' 2>/dev/null || true
pkill -9 -f 'GalaxyClient Helper' 2>/dev/null || true
pkill -9 -f 'GalaxyClient' 2>/dev/null || true
pkill -9 -f 'GalaxyUpdater' 2>/dev/null || true
pkill -9 -f 'GalaxyInstaller' 2>/dev/null || true
pkill -9 -f 'GalaxySetup' 2>/dev/null || true
pkill -9 -f 'GalaxyWebInstaller' 2>/dev/null || true
pkill -9 -f 'GOG_Galaxy_' 2>/dev/null || true
pkill -9 -f 'GOG Galaxy' 2>/dev/null || true
pkill -9 -f "${BOTTLE_ID}.*[Ss]team" 2>/dev/null || true
pkill -9 -f "${BOTTLE_DIR}.*steam\.exe" 2>/dev/null || true
pkill -9 -f "${BOTTLE_ID}.*wine" 2>/dev/null || true
pkill -9 -f 'winedevice\.exe' 2>/dev/null || true
pkill -9 -f 'wine64-preloader' 2>/dev/null || true
pkill -9 -f 'wine64\.exe' 2>/dev/null || true
sleep 1
if [[ -d "${BOTTLE_DIR}" ]]; then
export WINEPREFIX="${BOTTLE_DIR}"
[[ -x "${WINE_SERV}" ]] && "${WINE_SERV}" -k 2>/dev/null || true
sleep 1
gog_remove_lock_files
fi
sleep 1
}
gog_galaxy_running() {
pgrep -f 'GalaxyClient\.exe' >/dev/null 2>&1 \
|| pgrep -f 'GalaxyClient Helper' >/dev/null 2>&1
}
gog_wait_log_line() {
local log="$1" pattern="$2" timeout="${3:-30}"
local i
for i in $(seq 1 "${timeout}"); do
[[ -f "${log}" ]] && grep -q "${pattern}" "${log}" 2>/dev/null && return 0
sleep 1
done
return 1
}
gog_mac_activate_wine() {
osascript -e '
tell application "System Events"
repeat with proc in (every process whose name contains "wine" or name contains "Wine")
try
set frontmost of proc to true
exit repeat
end try
end repeat
end tell' 2>/dev/null || true
}

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Полный сетап GOG-bottle + web-stub. Аргумент: путь к GOG_Galaxy_*.exe
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
STUB="${1:-}"
if [[ ! -f "${GOG_DIR}/bottle.env" ]]; then
bash "${GOG_DIR}/create-bottle.sh" GOG-Install
fi
bash "${GOG_DIR}/setup-bottle.sh" --force
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
if [[ ! -f "${BOTTLE_DIR}/.gog_gdiplus_installed" ]]; then
bash "${GOG_DIR}/install-gdiplus.sh"
fi
command -v cabextract >/dev/null || brew install cabextract
bash "${GOG_DIR}/install-corefonts.sh"
bash "${GOG_DIR}/install-galaxy-extract.sh"
if [[ -z "${STUB}" ]]; then
echo "Готово. Установка JA3:"
echo " bash ${GOG_DIR}/launch-galaxy.sh # вход → Install в библиотеке"
echo " bash ${GOG_DIR}/finish-ja3-install.sh"
exit 0
fi
if [[ "${GOG_USE_WEB_STUB:-0}" == "1" ]]; then
echo "Web-stub (эксперимент; GalaxySetup часто падает на gdi32 в Wine)…"
bash "${GOG_DIR}/run-web-installer.sh" "${STUB}"
else
echo "Galaxy уже распакован. Для игры — launch-galaxy.sh (не web-stub)."
echo "Принудительно stub: GOG_USE_WEB_STUB=1 bash $0 ${STUB}"
bash "${GOG_DIR}/launch-galaxy.sh"
fi

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
# shellcheck source=wine-fonts.sh
source "${GOG_DIR}/wine-fonts.sh"
CACHE="${GAMES_CACHE_DIR}/corefonts"
FONTS="${BOTTLE_DIR}/drive_c/windows/Fonts"
cmd_fetch() {
local name="$1" url="$2"
mkdir -p "${CACHE}"
if [[ -f "${CACHE}/${name}" ]] && [[ "$(wc -c <"${CACHE}/${name}")" -gt 10000 ]]; then
return 0
fi
echo "Скачиваю ${name}"
curl -fsSL --retry 3 -o "${CACHE}/${name}" "${url}" || return 1
}
cmd_extract_cab() {
local cab="$1" out="${CACHE}/extract-${2:-x}"
rm -rf "${out}"
mkdir -p "${out}"
cabextract -d "${out}" "${cab}"
find "${out}" -iname '*.ttf' -exec cp -n {} "${FONTS}/" \;
}
gog_wine_check
command -v cabextract >/dev/null || { echo "brew install cabextract"; exit 1; }
mkdir -p "${FONTS}" "${CACHE}"
cmd_fetch andale32.exe "https://downloads.sourceforge.net/corefonts/andale32.exe" \
|| cmd_fetch andale32.exe "https://ftp.debian.org/debian/pool/contrib/m/msttcorefonts/andale32.exe" || true
cmd_fetch arial32.exe "https://downloads.sourceforge.net/corefonts/arial32.exe" \
|| cmd_fetch arial32.exe "https://ftp.debian.org/debian/pool/contrib/m/msttcorefonts/arial32.exe" || true
cmd_fetch times32.exe "https://downloads.sourceforge.net/corefonts/times32.exe" \
|| cmd_fetch times32.exe "https://ftp.debian.org/debian/pool/contrib/m/msttcorefonts/times32.exe" || true
for cab in andale32.exe arial32.exe times32.exe; do
[[ -f "${CACHE}/${cab}" ]] && [[ "$(wc -c <"${CACHE}/${cab}")" -gt 10000 ]] \
&& cmd_extract_cab "${CACHE}/${cab}" "${cab%.exe}" || true
done
gog_wine_ensure_fonts 1
touch "${BOTTLE_DIR}/.gog_corefonts_installed"
echo "OK: corefonts в GOG bottle ($(ls "${FONTS}" | wc -l | tr -d ' ') файлов)"

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# DXVK-macOS в GOG-bottle (для JA3).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
# install-dxvk-macos.sh привязан к HoMM config — дублируем install для GOG-bottle
DXVK_CACHE="${GAMES_DIR}/cache/dxvk-macos"
DXVK_TAR="${DXVK_CACHE}/dxvk-macOS-async.tar.gz"
DXVK_URL="https://github.com/Gcenx/DXVK-macOS/releases/download/v1.10.3-20230507-repack/dxvk-macOS-async-v1.10.3-20230507-repack.tar.gz"
DXVK_DIR="${DXVK_CACHE}/dxvk-macOS-async-v1.10.3-20230507-repack"
DXVK_X64="${DXVK_DIR}/x64"
WINE_LIB64="${WHISKY_LIBS}/Libraries/Wine/lib/wine/x86_64-windows"
SYS32="${BOTTLE_DIR}/drive_c/windows/system32"
gog_wine_check
mkdir -p "${DXVK_CACHE}"
[[ -f "${DXVK_TAR}" ]] || curl -L --fail -o "${DXVK_TAR}" "${DXVK_URL}"
[[ -f "${DXVK_X64}/d3d11.dll" ]] || tar -xzf "${DXVK_TAR}" -C "${DXVK_CACHE}"
cp -f "${WINE_LIB64}/dxgi.dll" "${SYS32}/dxgi.dll"
cp -f "${DXVK_X64}/d3d11.dll" "${SYS32}/d3d11.dll"
cp -f "${DXVK_X64}/d3d10core.dll" "${SYS32}/d3d10core.dll"
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d11 /d native,b /f >/dev/null
"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d10core /d native,b /f >/dev/null
"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\Direct3D' /v renderer /t REG_SZ /d vulkan /f >/dev/null 2>&1 || true
# Whisky UI: DXVK для HoMM; JA3 предпочитает D3DMetal (configure-ja3-d3dmetal.sh)
if [[ -f "${BOTTLE_DIR}/Metadata.plist" ]]; then
/usr/libexec/PlistBuddy -c 'Set :dxvkConfig:dxvk true' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || \
/usr/libexec/PlistBuddy -c 'Add :dxvkConfig:dxvk bool true' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true
fi
echo "OK: DXVK-macOS в ${BOTTLE_NAME}"

View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# GOG Galaxy в GOG-bottle без Wine-Inno (gdi32/botva2). Как install-gog-galaxy.sh для HoMM.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
GOG_SETUP="${GAMES_CACHE_DIR:-${GAMES_DIR}/cache}/setup_galaxy.exe"
EXTRACT_DIR="${GAMES_CACHE_DIR:-${GAMES_DIR}/cache}/gog-galaxy-extract-gog-bottle"
GOG_PF="${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy"
GOG_PD="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy"
MARKER="${BOTTLE_DIR}/.gog_galaxy_extract_installed"
gog_galaxy_exe() {
if [[ -f "${GOG_PF}/GalaxyClient.exe" ]]; then
echo "${GOG_PF}/GalaxyClient.exe"
return 0
fi
local found
found="$(find "${BOTTLE_DIR}/drive_c/Program Files"* -maxdepth 3 -name 'GalaxyClient.exe' 2>/dev/null | head -1)"
[[ -n "${found}" ]] && echo "${found}" && return 0
return 1
}
homm_gog_setup_url() {
if [[ -n "${GOG_SETUP_URL:-}" ]]; then
echo "${GOG_SETUP_URL}"
return 0
fi
local json url
json="$(curl -fsSL "https://remote-config.gog.com/components/webinstaller?component_version=2.0.0")"
url="$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["content"]["windows"]["downloadLink"])' <<<"${json}" 2>/dev/null)" || true
if [[ -z "${url}" ]]; then
url="https://content-system.gog.com/open_link/download?path=/open/galaxy/client/setup_galaxy_2.0.100.1.exe"
fi
echo "${url}"
}
main() {
gog_wine_check
gog_disk_guard 3 || exit 1
if gog_galaxy_exe >/dev/null 2>&1; then
echo "GOG Galaxy уже в bottle: $(gog_galaxy_exe)"
exit 0
fi
if [[ ! -f "${GOG_SETUP}" ]]; then
mkdir -p "$(dirname "${GOG_SETUP}")"
dl_url="$(homm_gog_setup_url)"
echo "Скачиваю setup_galaxy.exe (~290 MB)…"
curl -L --fail --retry 3 -C - -o "${GOG_SETUP}" "${dl_url}"
fi
if ! command -v innoextract >/dev/null 2>&1; then
echo "Нужен innoextract: brew install innoextract"
exit 1
fi
echo "Распаковка GOG Galaxy (innoextract) → ${BOTTLE_NAME}"
rm -rf "${EXTRACT_DIR}"
mkdir -p "${EXTRACT_DIR}"
innoextract -e -d "${EXTRACT_DIR}" "${GOG_SETUP}"
mkdir -p "${GOG_PF}" "${GOG_PD}"
cp -R "${EXTRACT_DIR}/app/." "${GOG_PF}/"
local pd_src
pd_src="$(find "${EXTRACT_DIR}" -type d -path '*/GOG.com/Galaxy' 2>/dev/null | head -1)"
if [[ -n "${pd_src}" ]]; then
cp -R "${pd_src}/." "${GOG_PD}/"
fi
if ! gog_galaxy_exe >/dev/null 2>&1; then
echo "❌ GalaxyClient.exe не найден после распаковки"
exit 1
fi
echo "VC++ redist (из setup_galaxy)…"
local vc_x86="${EXTRACT_DIR}/tmp/VC_redist.x86.exe"
local vc_x64="${EXTRACT_DIR}/tmp/VC_redist.x64.exe"
if [[ -f "${vc_x86}" ]]; then
export_wine_env_gog
cp -f "${vc_x86}" "${BOTTLE_DIR}/drive_c/VC_redist.x86.exe"
cp -f "${vc_x64}" "${BOTTLE_DIR}/drive_c/VC_redist.x64.exe"
"${WINE64}" C:\\VC_redist.x86.exe /install /quiet /norestart
"${WINE64}" C:\\VC_redist.x64.exe /install /quiet /norestart
fi
local fonts_dst="${BOTTLE_DIR}/drive_c/windows/Fonts"
mkdir -p "${fonts_dst}"
cp -f "${EXTRACT_DIR}"/fonts/*.ttf "${fonts_dst}/" 2>/dev/null || true
bash "${GOG_DIR}/register-galaxy-registry.sh"
date -u +%Y-%m-%dT%H:%M:%SZ >"${MARKER}"
echo "OK: $(gog_galaxy_exe)"
}
main "$@"

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# MS gdiplus.dll — без этого .NET падает на GenericSansSerif (GdipGetGenericFontFamilySansSerif).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
gog_wine_check
command -v winetricks >/dev/null || { echo "brew install winetricks"; exit 1; }
export WINE="${WINE64}"
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
export WINEDEBUG=-all
echo "winetricks gdiplus (~35 мин, скачает win7sp1)…"
winetricks -q gdiplus
touch "${BOTTLE_DIR}/.gog_gdiplus_installed"
echo "OK: gdiplus в ${BOTTLE_NAME}"

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Apple D3DMetal (GPTK) в Whisky Libraries — только framework + unix-мост.
# НЕ ditto всего GPTK lib (перезапишет wine64 на 7.7 и сломает bottle).
# Канон: https://github.com/Whisky-App/Whisky/issues/124
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
GPTK_APP="${GPTK_APP:-/Applications/Game Porting Toolkit.app}"
GPTK_WINE="${GPTK_APP}/Contents/Resources/wine"
WHISKY_WINE="${WHISKY_LIBS}/Libraries/Wine"
EXT="${WHISKY_WINE}/lib/external"
WINE_UNIX="${WHISKY_WINE}/lib/wine/x86_64-unix"
[[ -d "${GPTK_WINE}/lib/external/D3DMetal.framework" ]] || {
echo "❌ Нет Game Porting Toolkit.app"
echo " brew install --cask gcenx/wine/game-porting-toolkit"
exit 1
}
mkdir -p "${EXT}"
ditto "${GPTK_WINE}/lib/external/D3DMetal.framework" "${EXT}/D3DMetal.framework"
cp -f "${GPTK_WINE}/lib/external/libd3dshared.dylib" "${EXT}/libd3dshared.dylib"
# @rpath @loader_path — копия в корень lib/ (fixmywhisky)
cp -Rf "${EXT}/D3DMetal.framework" "${WHISKY_WINE}/lib/D3DMetal.framework"
cp -f "${EXT}/libd3dshared.dylib" "${WHISKY_WINE}/lib/libd3dshared.dylib"
fix_d3dmetal_fw() {
local fw="$1"
[[ -d "${fw}/Versions" ]] || return 0
rm -f "${fw}/D3DMetal" "${fw}/Resources" 2>/dev/null || true
ln -sf Versions/Current/D3DMetal "${fw}/D3DMetal"
ln -sf Versions/Current/Resources "${fw}/Resources"
chmod +x "${fw}/Versions/Current/D3DMetal" 2>/dev/null || true
xattr -d com.apple.quarantine "${fw}" 2>/dev/null || true
}
fix_d3dmetal_fw "${EXT}/D3DMetal.framework"
fix_d3dmetal_fw "${WHISKY_WINE}/lib/D3DMetal.framework"
chmod +x "${EXT}/libd3dshared.dylib" "${WHISKY_WINE}/lib/libd3dshared.dylib" 2>/dev/null || true
xattr -d com.apple.quarantine "${EXT}/libd3dshared.dylib" 2>/dev/null || true
# Мост D3DMetal: unix .so → libd3dshared. PE d3d12/dxgi — от Gcenx Wine 11.x (не GPTK 7.7).
mkdir -p "${WINE_UNIX}"
ln -sf ../../external/libd3dshared.dylib "${WINE_UNIX}/d3d12.so"
ln -sf ../../external/libd3dshared.dylib "${WINE_UNIX}/dxgi.so"
touch "${WHISKY_LIBS}/Libraries/.gptk_installed"
echo "OK: GPTK D3DMetal + d3d12/dxgi bridge → ${WHISKY_WINE}"
echo " Запуск: bash ${GOG_DIR}/prepare-ja3-d3dmetal.sh && bash ${GOG_DIR}/run-ja3.sh"

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# DXMT (D3D10/11 → Metal) для JA3 без GPTK. JA3 на D3D12 всё равно нужен GPTK; скрипт + -graphicsapi d3d11.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
DXMT_CACHE="${GAMES_DIR}/cache/dxmt"
DXMT_TAR="${DXMT_CACHE}/dxmt-v0.80-builtin.tar.gz"
DXMT_URL="https://github.com/3Shain/dxmt/releases/download/v0.80/dxmt-v0.80-builtin.tar.gz"
WINE_LIB="${WHISKY_LIBS}/Libraries/Wine/lib"
WINE_LIB64="${WINE_LIB}/wine/x86_64-windows"
WINE_UNIX="${WINE_LIB}/wine/x86_64-unix"
SYS32="${BOTTLE_DIR}/drive_c/windows/system32"
gog_wine_check
mkdir -p "${DXMT_CACHE}" "${SYS32}"
[[ -f "${DXMT_TAR}" ]] || curl -L --fail -o "${DXMT_TAR}" "${DXMT_URL}"
tar -xzf "${DXMT_TAR}" -C "${DXMT_CACHE}"
# builtin layout: bin/ or root with dlls — find files
DXMT_ROOT="${DXMT_CACHE}"
for sub in dxmt-v0.80-builtin bin .; do
[[ -f "${DXMT_CACHE}/${sub}/d3d11.dll" ]] && DXMT_ROOT="${DXMT_CACHE}/${sub}" && break
done
[[ -f "${DXMT_ROOT}/d3d11.dll" ]] || DXMT_ROOT="$(find "${DXMT_CACHE}" -name d3d11.dll -print -quit | xargs dirname)"
install_one() {
local src="$1" dest="$2"
[[ -f "${src}" ]] || return 0
cp -f "${src}" "${dest}/"
}
install_one "${DXMT_ROOT}/winemetal.so" "${WINE_UNIX}"
install_one "${DXMT_ROOT}/winemetal.dll" "${WINE_LIB64}"
install_one "${DXMT_ROOT}/winemetal.dll" "${SYS32}"
install_one "${DXMT_ROOT}/d3d11.dll" "${WINE_LIB64}"
install_one "${DXMT_ROOT}/dxgi.dll" "${WINE_LIB64}"
install_one "${DXMT_ROOT}/d3d10core.dll" "${WINE_LIB64}"
# Только wine lib + system32 (не в папку игры — иначе конфликт с overrides).
install_one "${DXMT_ROOT}/d3d11.dll" "${SYS32}"
install_one "${DXMT_ROOT}/dxgi.dll" "${SYS32}"
install_one "${DXMT_ROOT}/d3d10core.dll" "${SYS32}"
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d12 /d disabled /f >/dev/null 2>&1 || true
"${WINE64}" reg delete 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d11 /f >/dev/null 2>&1 || true
"${WINE64}" reg delete 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d10core /f >/dev/null 2>&1 || true
"${WINE64}" reg delete 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v dxgi /f >/dev/null 2>&1 || true
touch "${BOTTLE_DIR}/.ja3_dxmt_installed"
if [[ -f "${BOTTLE_DIR}/Metadata.plist" ]]; then
/usr/libexec/PlistBuddy -c 'Set :dxvkConfig:dxvk false' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true
/usr/libexec/PlistBuddy -c 'Set :performanceConfig:forceD3D11 true' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true
fi
echo "OK: DXMT v0.80 в ${BOTTLE_NAME} (+ game dir)"

View File

@@ -0,0 +1,203 @@
#!/usr/bin/env bash
# JA3: multipart GOG → innoextract в bottle (без Wine-Inno). Экономия диска: .bin удаляются по ходу.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
JA3_GAMES_DIR="${JA3_GAMES_DIR:-${HOME}/Games}"
JA3_PREFIX='setup_jagged_alliance_3_1.5.2_(80730)'
JA3_EXE="${JA3_GAMES_DIR}/${JA3_PREFIX}.exe"
JA3_TARGET="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3"
MARKER="${BOTTLE_DIR}/.ja3_installed"
LOG="/tmp/ja3-innoextract-${BOTTLE_NAME}.log"
JA3_MIN_FREE_MB="${JA3_MIN_FREE_MB:-800}"
JA3_KEEP_BINS="${JA3_KEEP_BINS:-0}"
gog_disk_free_mb() {
local kb
kb="$(gog_disk_free_kb)"
echo $((kb / 1024))
}
gog_disk_watch() {
local min_mb="$1"
local free
free="$(gog_disk_free_mb)"
if [[ "${free}" -lt "${min_mb}" ]]; then
echo "❌ Мало места на диске: ${free} MB свободно (нужно ≥${min_mb} MB)" >&2
df -h "$(gog_data_volume_path)" | awk 'NR==2' >&2
return 1
fi
return 0
}
gog_ja3_safe_cleanup() {
local freed=0
local d f
for d in \
"${GAMES_CACHE_DIR}/gog-galaxy-extract-gog-bottle" \
"${GAMES_CACHE_DIR}/ja3-extract-test" \
"${GAMES_CACHE_DIR}/ja3-innoextract-staging"; do
if [[ -d "${d}" ]]; then
local sz
sz="$(du -sk "${d}" | awk '{print $1}')"
rm -rf "${d}"
freed=$((freed + sz))
echo " удалён cache: ${d}"
fi
done
if [[ -f "${GAMES_CACHE_DIR}/setup_galaxy.exe" ]] \
&& [[ -f "${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy/GalaxyClient.exe" ]]; then
f="${GAMES_CACHE_DIR}/setup_galaxy.exe"
sz="$(du -sk "${f}" | awk '{print $1}')"
rm -f "${f}"
freed=$((freed + sz))
echo " удалён setup_galaxy.exe (Galaxy уже в bottle)"
fi
if [[ "${freed}" -gt 0 ]]; then
echo "Освобождено ~$((freed / 1024)) MB (cache)"
fi
}
ja3_bin_path() {
echo "${JA3_GAMES_DIR}/${JA3_PREFIX}-$1.bin"
}
ja3_delete_bin() {
local n="$1"
local p
p="$(ja3_bin_path "${n}")"
if [[ -f "${p}" ]]; then
rm -f "${p}"
echo " 🗑 удалён $(basename "${p}") (~$(du -sh "${p}" 2>/dev/null | cut -f1 || echo '?'))"
gog_disk_watch "${JA3_MIN_FREE_MB}" || true
fi
}
ja3_delete_all_bins() {
local n
for n in 1 2 3 4; do
ja3_delete_bin "${n}" || true
done
if [[ -f "${JA3_EXE}" && "${JA3_KEEP_BINS}" != "1" ]]; then
rm -f "${JA3_EXE}"
echo " 🗑 удалён $(basename "${JA3_EXE}")"
fi
}
ja3_watch_bins() {
local line opened=0
while IFS= read -r line; do
printf '%s\n' "${line}"
case "${line}" in
*"Opening \"${JA3_PREFIX}-2.bin\""*)
ja3_delete_bin 1
opened=2
;;
*"Opening \"${JA3_PREFIX}-3.bin\""*)
ja3_delete_bin 2
opened=3
;;
*"Opening \"${JA3_PREFIX}-4.bin\""*)
ja3_delete_bin 3
opened=4
;;
esac
gog_disk_watch "${JA3_MIN_FREE_MB}" || return 1
done
if [[ "${opened}" -ge 4 ]]; then
ja3_delete_bin 4 || true
fi
}
verify() {
local n
[[ -f "${JA3_EXE}" ]] || {
echo "❌ Нет: ${JA3_EXE}"
return 1
}
for n in 1 2 3 4; do
[[ -f "$(ja3_bin_path "${n}")" ]] || {
echo "❌ Нет: $(ja3_bin_path "${n}")"
return 1
}
done
command -v innoextract >/dev/null 2>&1 || {
echo "❌ brew install innoextract"
return 1
}
}
main() {
verify || exit 1
gog_wine_check
if [[ -f "${JA3_TARGET}/JA3.exe" ]]; then
echo "JA3 уже установлен: ${JA3_TARGET}/JA3.exe"
exit 0
fi
bash "${GOG_DIR}/emergency-stop.sh" || true
echo "=== Очистка cache (без трогания архивов игры) ==="
gog_ja3_safe_cleanup
gog_disk_watch "${JA3_MIN_FREE_MB}" || {
echo "❌ Перед установкой нужно ≥$((JA3_MIN_FREE_MB / 1024)) GB свободно."
exit 1
}
echo ""
echo "Свободно на Data: $(gog_disk_free_mb) MB"
echo "Распаковка JA3 → ${JA3_TARGET}"
echo "Лог: ${LOG}"
echo "(архивы .bin удаляются по мере чтения innoextract)"
echo ""
rm -rf "${JA3_TARGET}"
mkdir -p "${JA3_TARGET}"
: >"${LOG}"
cd "${JA3_GAMES_DIR}"
set +e
innoextract -e -m -d "${JA3_TARGET}" "$(basename "${JA3_EXE}")" 2>&1 \
| tee "${LOG}" | ja3_watch_bins
local rc=${PIPESTATUS[0]}
set -e
if [[ "${rc}" -ne 0 ]]; then
echo ""
echo "❌ innoextract exit ${rc}"
tail -15 "${LOG}" 2>/dev/null || true
df -h "$(gog_data_volume_path)" | awk 'NR==2'
if [[ "${JA3_CLEANUP_PARTIAL:-1}" == "1" ]]; then
echo "Удаляю незавершённую распаковку в bottle…"
rm -rf "${JA3_TARGET}"
fi
exit 1
fi
if [[ -f "${JA3_TARGET}/JA3.exe" ]]; then
bash "${GOG_DIR}/finish-ja3-layout.sh"
date -u +"%Y-%m-%dT%H:%M:%SZ" >"${MARKER}"
if [[ "${JA3_KEEP_BINS}" != "1" ]]; then
echo ""
echo "Удаляю GOG-архивы (~15 GB)…"
ja3_delete_all_bins
fi
echo ""
echo "OK: JA3 установлен."
echo " Свободно: $(gog_disk_free_mb) MB"
echo " Запуск: bash ${GOG_DIR}/run-ja3.sh"
else
echo "❌ JA3.exe не найден"
find "${JA3_TARGET}" -maxdepth 2 -iname 'JA3.exe' 2>/dev/null | head -5
exit 1
fi
}
main "$@"

View File

@@ -0,0 +1,152 @@
#!/usr/bin/env bash
# JA3 из GOG offline backup: setup_*.exe + setup_*-1.bin … -4.bin в одной папке.
# Galaxy не нужен. Установщик читает .bin рядом с .exe (не копируем 15 GB в bottle).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
JA3_GAMES_DIR="${JA3_GAMES_DIR:-${HOME}/Games}"
JA3_SETUP_EXE="${JA3_SETUP_EXE:-}"
LOG="/tmp/ja3-multipart-install-${BOTTLE_NAME}.log"
# Имена GOG v1.5.2 (80730); при другой версии задайте JA3_SETUP_EXE.
JA3_EXE_DEFAULT="${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730).exe"
JA3_BINS=(
"${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-1.bin"
"${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-2.bin"
"${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-3.bin"
"${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-4.bin"
)
gog_unix_to_win() {
local u="$1"
if [[ -x "${WINEPATH_BIN}" ]]; then
"${WINEPATH_BIN}" -w "${u}"
else
# fallback: Z: = /
local rest="${u#/}"
echo "Z:\\\\${rest//\//\\\\}"
fi
}
find_setup_exe() {
if [[ -n "${JA3_SETUP_EXE}" && -f "${JA3_SETUP_EXE}" ]]; then
echo "${JA3_SETUP_EXE}"
return 0
fi
if [[ -f "${JA3_EXE_DEFAULT}" ]]; then
echo "${JA3_EXE_DEFAULT}"
return 0
fi
local f
f="$(find "${JA3_GAMES_DIR}" -maxdepth 1 -iname 'setup*jagged*alliance*3*.exe' -print -quit 2>/dev/null || true)"
[[ -n "${f}" && -f "${f}" ]] && echo "${f}" && return 0
return 1
}
usage() {
cat <<EOF
Использование: bash $0
Папка с архивами (по умолчанию): ${JA3_GAMES_DIR}
Нужны 5 файлов в одной папке:
setup_jagged_alliance_3_1.5.2_(80730).exe (~1 MB, лаунчер Inno)
setup_jagged_alliance_3_1.5.2_(80730)-1.bin … -4.bin
Скачать недостающий .exe: https://www.gog.com/account → JA3 → offline backup (первая, ~1 MB часть).
Или:
lgogdownloader --login
bash ${GOG_DIR}/fetch-ja3-setup-exe.sh
Переменные: JA3_GAMES_DIR, JA3_SETUP_EXE
EOF
}
verify_parts() {
local exe="$1"
local missing=0
local b
if [[ ! -d "${JA3_GAMES_DIR}" ]]; then
echo "❌ Нет каталога: ${JA3_GAMES_DIR}"
return 1
fi
for b in "${JA3_BINS[@]}"; do
if [[ ! -f "${b}" ]]; then
echo "❌ Нет: ${b}"
missing=1
fi
done
if [[ ! -f "${exe}" ]]; then
echo "❌ Нет лаунчера .exe (~1 MB) в ${JA3_GAMES_DIR}"
echo " У вас есть 4× .bin — без .exe установка GOG не стартует."
echo " Скачайте с account.gog.com первую часть offline backup (installer0, ~1 MB)."
echo " Или: bash ${GOG_DIR}/fetch-ja3-setup-exe.sh"
missing=1
fi
[[ "${missing}" -eq 0 ]]
}
main() {
local exe win_exe
exe="$(find_setup_exe)" || exe=""
if ! verify_parts "${exe}"; then
echo ""
usage
exit 1
fi
bash "${GOG_DIR}/emergency-stop.sh" || true
gog_swap_guard "${GOG_SWAP_MAX_MB:-400}" || exit 1
# bins (~15 GB) + распаковка в bottle (~16 GB) — пик ~2530 GB на Data
gog_disk_guard "${JA3_MIN_DISK_GB:-25}" || {
echo "❌ Мало места: нужно ≥25 GB свободно на Data (архивы + игра в bottle)."
echo " Сейчас: $(df -h "$(gog_data_volume_path)" | awk 'NR==2 {print $4 " свободно, " $5 " занято"}')"
exit 1
}
export_wine_env_gog
gog_wine_check
win_exe="$(gog_unix_to_win "${exe}")"
SILENT=(/VERYSILENT /SUPPRESSMSGBOXES /NORESTART)
echo "Установка JA3 из multipart (без Galaxy)"
echo " Папка: ${JA3_GAMES_DIR}"
echo " Wine: ${win_exe}"
echo " Лог: ${LOG}"
: >"${LOG}"
# Inno ищет .bin в каталоге установщика — запуск из JA3_GAMES_DIR
cd "${JA3_GAMES_DIR}"
if ! "${WINE64}" "${win_exe}" "${SILENT[@]}" >>"${LOG}" 2>&1; then
echo "⚠️ Wine-Inno не прошёл — пробуем innoextract (без botva2/gdi32)…"
if command -v innoextract >/dev/null 2>&1; then
exec bash "${GOG_DIR}/install-ja3-innoextract.sh"
fi
echo "❌ Установщик завершился с ошибкой:"
strings "${LOG}" 2>/dev/null | tail -20 || tail -20 "${LOG}"
exit 1
fi
if [[ -f "${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3/JA3.exe" ]]; then
date -u +"%Y-%m-%dT%H:%M:%SZ" >"${BOTTLE_DIR}/.ja3_installed"
echo ""
echo "OK: JA3 установлен."
echo " Запуск: bash ${GOG_DIR}/run-ja3.sh"
echo " После проверки игры можно удалить .bin в ${JA3_GAMES_DIR} (~15 GB)."
else
echo "⚠️ JA3.exe не найден в bottle — tail лога:"
tail -40 "${LOG}"
exit 1
fi
}
main "$@"

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# JA3 из GOG offline backup installer — без Galaxy. Inno /VERYSILENT.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
INSTALLER="${1:-}"
LOG="/tmp/ja3-offline-install-${BOTTLE_NAME}.log"
usage() {
cat <<EOF
Использование: bash $0 /path/to/setup_jagged_alliance_3_*.exe
Скачать (Windows offline backup):
https://www.gog.com/account → Jagged Alliance 3 → Download offline backup game installers
Документация: docs/games/JA3_MAC_WITHOUT_GALAXY.md
EOF
}
if [[ -z "${INSTALLER}" || ! -f "${INSTALLER}" ]]; then
usage
exit 1
fi
bash "${GOG_DIR}/emergency-stop.sh"
gog_swap_guard "${GOG_SWAP_MAX_MB:-400}" || exit 1
gog_disk_guard 40 || {
echo "❌ Для JA3 нужно ~40+ GB свободно на Data (игра + установка)."
exit 1
}
export_wine_env_gog
gog_wine_check
# Inno silent (GOG offline installers)
SILENT=(/VERYSILENT /SUPPRESSMSGBOXES /NORESTART)
local_base="$(basename "${INSTALLER}")"
cp -f "${INSTALLER}" "${BOTTLE_DIR}/drive_c/${local_base}"
win="C:\\${local_base}"
echo "Установка JA3 (без Galaxy): ${INSTALLER}"
echo "Лог: ${LOG}"
: >"${LOG}"
cd "${BOTTLE_DIR}/drive_c"
if ! "${WINE64}" "${win}" "${SILENT[@]}" >>"${LOG}" 2>&1; then
echo "❌ Установщик завершился с ошибкой. tail:"
tail -40 "${LOG}"
exit 1
fi
if [[ -f "${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3/JA3.exe" ]]; then
date -u +"%Y-%m-%dT%H:%M:%SZ" >"${BOTTLE_DIR}/.ja3_installed"
echo ""
echo "OK: JA3 установлен."
echo " Запуск: bash ${GOG_DIR}/run-ja3.sh"
else
echo "⚠️ JA3.exe пока не найден — проверьте лог, возможно нужны ещё offline .exe (DLC/base):"
tail -30 "${LOG}"
exit 1
fi

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# VC++ 2015 из __redist JA3 (innoextract не запускает установщики).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
JA3="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3"
VC_X86="${JA3}/__redist/MSVC2015/VC_redist.x86.exe"
VC_X64="${JA3}/__redist/MSVC2015_x64/VC_redist.x64.exe"
MARKER="${BOTTLE_DIR}/.ja3_redist_installed"
[[ -f "${JA3}/JA3.exe" ]] || {
echo "❌ Сначала install-ja3-innoextract.sh"
exit 1
}
if [[ -f "${MARKER}" ]]; then
echo "JA3 redist уже ставился: ${MARKER}"
exit 0
fi
export_wine_env_gog
gog_wine_check
for vc in "${VC_X86}" "${VC_X64}"; do
[[ -f "${vc}" ]] || continue
base="$(basename "${vc}")"
cp -f "${vc}" "${BOTTLE_DIR}/drive_c/${base}"
echo "VC++ ${base}"
"${WINE64}" "C:\\${base}" /install /quiet /norestart || true
done
date -u +"%Y-%m-%dT%H:%M:%SZ" >"${MARKER}"
echo "OK: VC++ redist для JA3"

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Довести установку JA3 через Wine-Inno (пост-скрипты GOG), если есть архивы в JA3_GAMES_DIR.
# innoextract одного не хватает для Lua/VFS — Wine-установщик создаёт правильный layout.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
JA3_GAMES_DIR="${JA3_GAMES_DIR:-${HOME}/Games}"
PREFIX='setup_jagged_alliance_3_1.5.2_(80730)'
EXE="${JA3_GAMES_DIR}/${PREFIX}.exe"
TARGET='C:\GOG Games\Jagged Alliance 3'
verify_archives() {
[[ -f "${EXE}" ]] || {
echo "❌ Нет ${EXE}"
echo " Положите 5 частей offline backup в ${JA3_GAMES_DIR}"
exit 1
}
local n
for n in 1 2 3 4; do
[[ -f "${JA3_GAMES_DIR}/${PREFIX}-${n}.bin" ]] || {
echo "❌ Нет ${JA3_GAMES_DIR}/${PREFIX}-${n}.bin"
exit 1
}
done
}
main() {
verify_archives
bash "${GOG_DIR}/emergency-stop.sh" || true
gog_swap_guard "${GOG_SWAP_MAX_MB:-800}" || exit 1
gog_disk_guard 20 || exit 1
export_wine_env_gog
gog_wine_check
local win
win="$("${WINEPATH_BIN}" -w "${EXE}")"
echo "Wine-установщик GOG (пост-скрипты, Lua.hpk)…"
echo " ${win}"
cd "${JA3_GAMES_DIR}"
if ! "${WINE64}" "${win}" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /DIR="${TARGET}"; then
echo "❌ Wine-Inno упал — см. docs/games/TROUBLESHOOTING.md § Setup gdi32"
echo " Запасной путь: innoextract + -unpacked (run-ja3.sh)"
exit 1
fi
bash "${GOG_DIR}/finish-ja3-layout.sh"
echo "OK. Запуск: bash ${GOG_DIR}/run-ja3.sh"
}
main "$@"

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# JA3 без GOG Galaxy: подготовка bottle + подсказка по offline installer.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "=== Jagged Alliance 3 без GOG Galaxy ==="
echo "Док: docs/games/JA3_MAC_WITHOUT_GALAXY.md"
echo ""
bash "${GOG_DIR}/emergency-stop.sh" || true
USED="$(bash -c 'source '"${GOG_DIR}"'/config.sh 2>/dev/null; gog_swap_used_mb' 2>/dev/null || echo 0)"
if [[ "${USED:-0}" -gt 400 ]] 2>/dev/null; then
echo "❌ Swap ~${USED}MB — сначала перезагрузите Mac, затем снова этот скрипт."
exit 1
fi
bash "${GOG_DIR}/setup-ja3-bottle.sh"
echo ""
echo "=== Скачайте offline installer (Windows) ==="
echo " https://www.gog.com/account → Jagged Alliance 3"
echo " → Download offline backup game installers (все части)"
echo ""
open "https://www.gog.com/account" 2>/dev/null || true
JA3_GAMES_DIR="${HOME}/Games"
ja3_bins_ok=1
for n in 1 2 3 4; do
[[ -f "${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730)-${n}.bin" ]] || ja3_bins_ok=0
done
if [[ "${ja3_bins_ok}" -eq 1 ]]; then
echo "Найдены 4× .bin в ${JA3_GAMES_DIR} — multipart offline backup."
if [[ -f "${JA3_GAMES_DIR}/setup_jagged_alliance_3_1.5.2_(80730).exe" ]] \
|| find "${JA3_GAMES_DIR}" -maxdepth 1 -iname 'setup*jagged*alliance*3*.exe' -print -quit 2>/dev/null | grep -q .; then
echo "Запуск install-ja3-innoextract.sh (innoextract, без Wine-Inno) …"
exec bash "${GOG_DIR}/install-ja3-innoextract.sh"
fi
echo "❌ Не хватает лаунчера .exe (~1 MB) рядом с .bin."
echo " Скачайте с https://www.gog.com/account или:"
echo " bash ${GOG_DIR}/fetch-ja3-setup-exe.sh"
exit 1
fi
# Автоустановка, если installer уже в Downloads
found=()
while IFS= read -r -d '' f; do
found+=("$f")
done < <(find "${HOME}/Downloads" -maxdepth 1 -iname 'setup*jagged*alliance*3*.exe' -print0 2>/dev/null)
if [[ ${#found[@]} -eq 0 ]]; then
echo "Когда .exe скачан (или все 5 частей в ~/Games):"
echo " bash ${GOG_DIR}/install-ja3-offline.sh ~/Downloads/setup_jagged_alliance_3_*.exe"
echo " bash ${GOG_DIR}/install-ja3-multipart.sh"
exit 0
fi
echo "Найдены installers в Downloads:"
printf ' %s\n' "${found[@]}"
echo ""
for f in "${found[@]}"; do
echo "Установка: ${f}"
bash "${GOG_DIR}/install-ja3-offline.sh" "${f}" || exit 1
done
echo ""
echo "Запуск: bash ${GOG_DIR}/run-ja3.sh"

View File

@@ -0,0 +1,6 @@
# JA3 на Apple Silicon + MoltenVK (Whisky / DXVK-macOS)
dxgi.hideIntelGpu = True
d3d11.enableFeatureLevel11_1 = True
d3d11.maxFeatureLevel = 12_1
d3d11.dxgiBindBackBufferAsRT = True
dxgi.deferSurfaceCreation = True

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Запуск GOG Galaxy с максимальным шансом увидеть окно на macOS:
# виртуальный desktop Wine + foreground в этом терминале.
set -euo pipefail
# Suppress verbose MoltenVK capability dump in foreground mode.
export MVK_CONFIG_LOG_LEVEL=0
export GOG_USE_WINEVDESKTOP=1
export GOG_LAUNCH_FG=1
exec bash "$(cd "$(dirname "$0")" && pwd)/launch-galaxy.sh" "$@"

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Запуск GOG Galaxy через Whisky GUI (pin gog-galaxy) — лучший шанс увидеть окно на macOS.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
bash "${GOG_DIR}/pin-galaxy-whisky.sh" >/dev/null 2>&1 || bash "${GOG_DIR}/pin-galaxy-whisky.sh"
bash "${GOG_DIR}/reset.sh"
echo "Открываю Whisky → bottle «${BOTTLE_NAME}»."
echo ""
echo "В GUI Whisky:"
echo " 1) Выберите bottle «${BOTTLE_NAME}»"
echo " 2) Pin «gog-galaxy» → Run (не GalaxySetup_full / не web-stub)"
echo " 3) Cmd+Tab → wine64 / GOG Galaxy"
echo ""
echo "Если pin нет: bash ${GOG_DIR}/pin-galaxy-whisky.sh"
echo "Логи: bash ${GOG_DIR}/collect-gog-logs-7h.sh"
echo ""
open -a Whisky 2>/dev/null || open -a "Whisky.app"

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env bash
# GOG Galaxy в GOG-bottle (CEF). По умолчанию — виртуальный desktop Wine (окно видно на Mac).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
# shellcheck source=gog-process.sh
source "${GOG_DIR}/gog-process.sh"
LOG="/tmp/gog-galaxy-client-${BOTTLE_NAME}.log"
GALAXY_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/GalaxyClient.log"
INIT_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/GalaxyInitialization.log"
CEF_LOG="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy/logs/cef.log"
gog_launch_wine() {
local win='C:\Program Files (x86)\GOG Galaxy\GalaxyClient.exe'
local -a args=()
mapfile -t args < <(gog_galaxy_cef_args_array)
cd "${BOTTLE_DIR}/drive_c"
if [[ "${GOG_USE_WINEVDESKTOP:-1}" == "1" ]]; then
local w h
IFS='x' read -r w h <<< "${GOG_VDESKTOP:-1280x800}"
echo "Режим: explorer /desktop=GOGGalaxy,${w}x${h}"
if [[ "${GOG_LAUNCH_FG:-0}" == "1" ]]; then
exec "${WINE64}" explorer "/desktop=GOGGalaxy,${w}x${h}" "${win}" "${args[@]}"
fi
nohup "${WINE64}" explorer "/desktop=GOGGalaxy,${w}x${h}" "${win}" "${args[@]}" >>"${LOG}" 2>&1 &
else
echo "Режим: прямой GalaxyClient (GOG_USE_WINEVDESKTOP=0)"
if [[ "${GOG_LAUNCH_FG:-0}" == "1" ]]; then
exec "${WINE64}" "${win}" "${args[@]}"
fi
nohup "${WINE64}" "${win}" "${args[@]}" >>"${LOG}" 2>&1 &
fi
}
main() {
gog_wine_check
gog_swap_guard "${GOG_SWAP_MAX_MB:-400}" || exit 1
gog_disk_guard 8 || exit 1
if [[ ! -f "${BOTTLE_DIR}/.gog_galaxy_extract_installed" ]]; then
echo "Сначала: bash ${GOG_DIR}/install-galaxy-extract.sh"
exit 1
fi
if ! find "${BOTTLE_DIR}/drive_c/Program Files"* -name 'GalaxyClient.exe' -print -quit 2>/dev/null | grep -q .; then
bash "${GOG_DIR}/install-galaxy-extract.sh"
fi
echo "Останавливаем старые процессы GOG Galaxy…"
gog_kill_all
pkill -f 'explorer.exe /desktop=GOGGalaxy' 2>/dev/null || true
if gog_galaxy_running; then
echo "❌ Galaxy всё ещё в процессах — повторите: bash ${GOG_DIR}/reset.sh"
pgrep -fl 'Galaxy' 2>/dev/null || true
exit 1
fi
export_wine_env_gog
echo "Bottle: ${BOTTLE_NAME}"
echo "Лог: ${LOG}"
: >"${LOG}"
if [[ "${GOG_LAUNCH_FG:-0}" == "1" ]]; then
echo ""
echo "Foreground: окно Wine привязано к этому терминалу. Закройте Galaxy — Ctrl+C."
echo "Ищите окно «GOGGalaxy» или полосу заголовка Wine desktop."
echo ""
gog_launch_wine
exit 0
fi
gog_launch_wine
echo "Запуск wine64…"
local ok=0 i
for i in $(seq 1 30); do
sleep 1
if grep -q 'Second client instance detected' "${INIT_LOG}" 2>/dev/null; then
if tail -5 "${INIT_LOG}" 2>/dev/null | grep -q 'The client will exit'; then
echo "❌ Второй экземпляр: bash ${GOG_DIR}/reset.sh && bash $0"
tail -8 "${INIT_LOG}"
exit 1
fi
fi
if [[ -f "${GALAXY_LOG}" ]] && grep -qE 'Showing login form|No refresh token found|will continue' "${GALAXY_LOG}" 2>/dev/null; then
ok=1
break
fi
if [[ -f "${INIT_LOG}" ]] && grep -q 'will continue' "${INIT_LOG}" 2>/dev/null; then
ok=1
break
fi
if ! gog_galaxy_running; then
sleep 2
if ! gog_galaxy_running; then
break
fi
fi
done
if ! gog_galaxy_running; then
echo "❌ GalaxyClient не остался в процессах:"
tail -25 "${LOG}"
[[ -f "${INIT_LOG}" ]] && tail -15 "${INIT_LOG}"
exit 1
fi
sleep 2
gog_mac_activate_wine || true
echo ""
echo "OK: GOG Galaxy запущен (процесс + login в логе — окно на Mac не гарантировано)."
echo " • Окно: bash ${GOG_DIR}/launch-galaxy-whisky.sh → Whisky → pin gog-galaxy → Run"
echo " • RCA/логи 7ч: docs/games/GOG_GALAXY_7H_RCA.md | collect-gog-logs-7h.sh"
if [[ "${GOG_USE_WINEVDESKTOP:-1}" == "1" ]]; then
echo " • Cmd+Tab → «wine64» / «GOGGalaxy» — отдельное окно виртуального стола Wine"
else
echo " • Cmd+Tab → «GalaxyClient» или «wine64»"
fi
echo "Не видно окна? bash ${GOG_DIR}/launch-galaxy-ui.sh (vdesktop + foreground)"
echo " • Whisky → «${BOTTLE_NAME}» → pin gog-galaxy → Run"
echo " • Лог: ${GALAXY_LOG}"
if [[ "${ok}" -eq 1 ]]; then
echo " • В логе login UI — войдите → Jagged Alliance 3 → Install"
if [[ -f "${CEF_LOG}" ]] && grep -q 'parent window' "${CEF_LOG}" 2>/dev/null; then
echo " • cef.log: «parent window» — типично для Wine; vdesktop/Whisky Run часто лечит"
fi
fi
}
main "$@"

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
echo "Whisky bottles (franke):"
for meta in "${HOME}/Library/Containers/com.franke.Whisky/Bottles/"*/Metadata.plist; do
[[ -f "${meta}" ]] || continue
id="$(basename "$(dirname "${meta}")")"
name="$(plutil -extract info.name raw "${meta}" 2>/dev/null || echo "?")"
du -sh "$(dirname "${meta}")" 2>/dev/null | awk -v id="${id}" -v n="${name}" '{printf " %s %-20s %s\n", id, n, $1}'
done
echo ""
echo "Привязать GOG: bash $(dirname "$0")/use-bottle.sh <UUID> [имя]"

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Pin GOG Galaxy в Whisky для bottle GOG-Install (как start.sh gog в HoMM).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
GOG_EXE="${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy/GalaxyClient.exe"
[[ -f "${GOG_EXE}" ]] || { echo "Нет GalaxyClient — install-galaxy-extract.sh"; exit 1; }
METADATA="${BOTTLE_DIR}/Metadata.plist"
PROG_SETTINGS="${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist"
GOG_URL="file://${BOTTLE_DIR}/drive_c/Program%20Files%20(x86)/GOG%20Galaxy/GalaxyClient.exe"
CEF="$(gog_galaxy_cef_args_array | tr '\n' ' ')"
python3 <<PY
import plistlib
from pathlib import Path
meta = Path("${METADATA}")
with meta.open("rb") as f:
data = plistlib.load(f)
pins = data.setdefault("info", {}).setdefault("pins", [])
if not any(p.get("name") == "gog-galaxy" for p in pins):
pins.append({
"name": "gog-galaxy",
"removable": False,
"url": {"relative": "${GOG_URL}"},
})
with meta.open("wb") as f:
plistlib.dump(data, f)
print("pin gog-galaxy added")
else:
print("pin exists")
ps = Path("${PROG_SETTINGS}")
ps.parent.mkdir(parents=True, exist_ok=True)
with ps.open("wb") as f:
plistlib.dump({"arguments": "${CEF}", "environment": {}, "locale": ""}, f)
print("OK:", ps)
PY
echo ""
echo "Whisky → ${BOTTLE_NAME} → pin «gog-galaxy» → Run"
echo "Или: bash ${GOG_DIR}/launch-galaxy.sh"

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# JA3 + D3DMetal: убрать DXVK/GPTK PE из system32, оставить builtin Gcenx + d3d12/dxgi.so → libd3dshared.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
SYS32="${BOTTLE_DIR}/drive_c/windows/system32"
WINE_PE="${WHISKY_LIBS}/Libraries/Wine/lib/wine/x86_64-windows"
WINE_UNIX="${WHISKY_LIBS}/Libraries/Wine/lib/wine/x86_64-unix"
BAK="${SYS32}/.ja3-d3dmetal-bak"
LS="${BOTTLE_DIR}/drive_c/users/${WINE_USER:-eternal}/AppData/Roaming/Jagged Alliance 3/LocalStorage.lua"
ja3_gptk_available || ja3_bottle_uses_d3dmetal || {
echo "❌ Нет GPTK/D3DMetal — configure-ja3-d3dmetal.sh"
exit 1
}
bash "${GOG_DIR}/install-gptk-whisky.sh"
mkdir -p "${BAK}"
# DXVK / GPTK PE в system32 ломают D3DMetal (114688/86016 → «graphics subsystem»).
for dll in d3d12.dll dxgi.dll d3d11.dll d3d10core.dll d3d12core.dll winemetal.dll; do
if [[ -f "${SYS32}/${dll}" ]]; then
mv -f "${SYS32}/${dll}" "${BAK}/${dll}" 2>/dev/null || true
fi
done
# Мост Metal + Gcenx PE (94208/200704). НЕ GPTK PE — __wine_unix_call на Wine 11.
# НЕ копировать в system32 DXVK/GPTK DLL.
mkdir -p "${WINE_UNIX}"
ln -sf ../../external/libd3dshared.dylib "${WINE_UNIX}/d3d12.so"
ln -sf ../../external/libd3dshared.dylib "${WINE_UNIX}/dxgi.so"
# system32: убрать overrides — builtin из Wine lib
for dll in d3d12.dll dxgi.dll d3d11.dll d3d10core.dll d3d12core.dll winemetal.dll; do
if [[ -f "${SYS32}/${dll}" ]]; then
mv -f "${SYS32}/${dll}" "${BAK}/${dll}" 2>/dev/null || true
fi
done
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WHISKY_LIBS}/Libraries/Wine/lib/external:${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
export WINED3DMETAL=1
export WINEDEBUG=-all
"${WINE64}" reg delete 'HKCU\Software\Wine\DllOverrides' /v d3d11 /f >/dev/null 2>&1 || true
"${WINE64}" reg delete 'HKCU\Software\Wine\DllOverrides' /v d3d10core /f >/dev/null 2>&1 || true
"${WINE64}" reg delete 'HKCU\Software\Wine\DllOverrides' /v dxgi /f >/dev/null 2>&1 || true
"${WINE64}" reg delete 'HKCU\Software\Wine\DllOverrides' /v d3d12 /f >/dev/null 2>&1 || true
"${WINE64}" reg delete 'HKCU\Software\Wine\Direct3D' /v renderer /f >/dev/null 2>&1 || true
if [[ -f "${LS}" ]]; then
perl -i -pe 's/^\t\tGraphicsApi = ".*"/\t\tGraphicsApi = "d3d12"/' "${LS}"
else
mkdir -p "$(dirname "${LS}")"
cat >"${LS}" <<'EOF'
return {
Options = {
GraphicsApi = "d3d12",
VideoPreset = "Low",
},
}
EOF
fi
if [[ -f "${BOTTLE_DIR}/Metadata.plist" ]]; then
/usr/libexec/PlistBuddy -c 'Set :dxvkConfig:dxvk false' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true
/usr/libexec/PlistBuddy -c 'Set :performanceConfig:forceD3D11 false' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || \
/usr/libexec/PlistBuddy -c 'Add :performanceConfig:forceD3D11 bool false' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true
/usr/libexec/PlistBuddy -c 'Set :graphicsConfig:backend d3dMetal' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true
/usr/libexec/PlistBuddy -c 'Set :metalConfig:sequoiaCompatMode true' "${BOTTLE_DIR}/Metadata.plist" 2>/dev/null || true
fi
touch "${BOTTLE_DIR}/.ja3_d3dmetal_prepared"
# Ярлык для Whisky Run (тот же CWD + D3DMetal env из Metadata)
cat >"${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3/run-ja3.cmd" <<'EOF'
@echo off
cd /d "C:\GOG Games\Jagged Alliance 3"
start "" "JA3.exe" -unpacked %*
EOF
echo "OK: D3DMetal graphics для ${BOTTLE_NAME}"
echo " Скрипт: bash ${GOG_DIR}/run-ja3.sh"
echo " Whisky Run: C:\\GOG Games\\Jagged Alliance 3\\run-ja3.cmd (не JA3.exe напрямую)"

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Удалить клиент Steam из GOG-bottle (если попал). JA3/GOG — без Steam.app.
# Не трогает steam_api64.dll в папке игры (GOG Galaxy wrapper, не Steam client).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
STEAM_DIRS=(
"${BOTTLE_DIR}/drive_c/Program Files (x86)/Steam"
"${BOTTLE_DIR}/drive_c/Program Files/Steam"
"${BOTTLE_DIR}/drive_c/Steam"
)
main() {
echo "=== Purge Steam из bottle ${BOTTLE_NAME} ==="
pkill -9 -f "${BOTTLE_ID}.*[Ss]team" 2>/dev/null || true
pkill -9 -f "${BOTTLE_DIR}.*steam\.exe" 2>/dev/null || true
local d removed=0
for d in "${STEAM_DIRS[@]}"; do
if [[ -d "${d}" ]]; then
echo " удаляю: ${d}"
rm -rf "${d}"
removed=1
fi
done
find "${BOTTLE_DIR}/drive_c" -maxdepth 4 \( -iname 'steam.exe' -o -iname 'steamservice.exe' \) 2>/dev/null | while read -r f; do
echo " удаляю: ${f}"
rm -f "${f}"
removed=1
done
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
if [[ -x "${WINE64}" ]]; then
"${WINE64}" reg delete 'HKCU\Software\Valve' /f >/dev/null 2>&1 || true
"${WINE64}" reg delete 'HKLM\Software\Valve' /f >/dev/null 2>&1 || true
fi
if [[ "${removed}" -eq 0 ]]; then
echo "OK: каталога Steam в bottle не было."
else
echo "OK: Steam client удалён из bottle."
fi
echo ""
echo "Примечание: JA3.exe использует steam_api64.dll + Galaxy.dll (GOG), это не Steam.app."
}
main "$@"

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Реестр после innoextract — web-installer видит установленный Galaxy.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
GOG_PF='C:\Program Files (x86)\GOG Galaxy'
CLIENT="${GOG_PF}\\GalaxyClient.exe"
SERVICE="${GOG_PF}\\GalaxyClientService.exe"
gog_wine_check
export_wine_env_gog
"${WINE64}" reg add 'HKLM\Software\WOW6432Node\GOG.com\GalaxyClient' /v version /t REG_SZ /d '2.0.100.1' /f
"${WINE64}" reg add 'HKLM\Software\WOW6432Node\GOG.com\GalaxyClient\paths' /v GalaxyClientExePath /t REG_SZ /d "${CLIENT}" /f
"${WINE64}" reg add 'HKLM\Software\WOW6432Node\GOG.com\GalaxyClient\paths' /v GalaxyClientServiceExePath /t REG_SZ /d "${SERVICE}" /f
"${WINE64}" reg add 'HKCU\Software\GOG.com\Galaxy' /f 2>/dev/null || true
echo "OK: GalaxyClient paths → ${CLIENT}"

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Реестр GOG для JA3 (innoextract не запускает Inno-скрипт с ключами).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
GAME_ID='1556263729'
JA3_WIN='C:\GOG Games\Jagged Alliance 3'
JA3_EXE="${JA3_WIN}\\JA3.exe"
KEY="HKLM\\Software\\WOW6432Node\\GOG.com\\Games\\${GAME_ID}"
gog_wine_check
export_wine_env_gog
"${WINE64}" reg add "${KEY}" /v gameName /t REG_SZ /d 'Jagged Alliance 3' /f
"${WINE64}" reg add "${KEY}" /v exe /t REG_SZ /d 'JA3.exe' /f
"${WINE64}" reg add "${KEY}" /v path /t REG_SZ /d "${JA3_WIN}\\" /f
"${WINE64}" reg add "${KEY}" /v startMenu /t REG_SZ /d 'Jagged Alliance 3\' /f
"${WINE64}" reg add "${KEY}" /v version /t REG_SZ /d '1.5.2' /f
"${WINE64}" reg add "${KEY}" /v language /t REG_SZ /d 'English' /f
"${WINE64}" reg add "${KEY}" /v launchParam /t REG_SZ /d '' /f
"${WINE64}" reg add 'HKLM\Software\WOW6432Node\GOG.com\DefaultPackPath' /ve /t REG_SZ /d 'C:\GOG Games' /f 2>/dev/null || true
bash "${GOG_DIR}/register-galaxy-registry.sh" 2>/dev/null || true
echo "OK: GOG registry → ${KEY}"

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Остановить JA3 / Wine (CrossOver bottle JA3-GOG) перед перезапуском.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=gog-process.sh
source "${GOG_DIR}/gog-process.sh"
bash "${GOG_DIR}/emergency-stop.sh" 2>/dev/null || gog_kill_all
pkill -f 'JA3\.exe' 2>/dev/null || true
pkill -f 'wineserver.*JA3-GOG' 2>/dev/null || true
CX="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver"
if [[ -x "${CX}/bin/wine" ]]; then
"${CX}/bin/wine" --bottle JA3-GOG wineboot --kill 2>/dev/null || true
fi
sleep 2
pgrep -fl 'JA3\.exe|wineserver' 2>/dev/null | head -5 || echo "OK: процессы JA3/Wine остановлены"

9
scripts/games/gog/reset.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Только GOG web-installer / Galaxy в этом bottle — без Steam, wineboot -k всего префикса.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=gog-process.sh
source "${GOG_DIR}/gog-process.sh"
gog_kill_all
echo "OK: GOG-процессы остановлены, lock снят (bottle ${BOTTLE_NAME})."

View File

@@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Запуск GalaxySetup.exe (JA3 и др.) без родительского GalaxyInstaller — обход «Galaxy already running» + gdi32.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
PRODUCT_ID="${1:-1556263729}"
GAME_NAME="${2:-Jagged Alliance 3}"
LOG="/tmp/gog-galaxy-setup-${BOTTLE_NAME}.log"
gog_kill_all_galaxy() {
pkill -f 'GOG_Galaxy_' 2>/dev/null || true
pkill -f 'GalaxyWebInstaller' 2>/dev/null || true
pkill -f 'GalaxyInstaller' 2>/dev/null || true
pkill -f 'GalaxySetup' 2>/dev/null || true
pkill -f "${BOTTLE_ID}.*Galaxy" 2>/dev/null || true
sleep 2
}
find_galaxy_setup() {
local d
for d in "${BOTTLE_DIR}/drive_c/users/"*/AppData/Local/Temp/GalaxyInstaller_*; do
[[ -f "${d}/GalaxySetup.exe" ]] || continue
echo "${d}/GalaxySetup.exe"
return 0
done
return 1
}
export_wine_env_gog_setup() {
export WINEPREFIX="${BOTTLE_DIR}"
export WINEMSYNC=1
export WINEESYNC=1
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
# Inno/botva2: native gdiplus ломает gdi32 в MsgBox; stub/.NET — gdiplus=n,b
export WINEDLLOVERRIDES="gdiplus=b"
export WINEDEBUG="${WINEDEBUG:--all}"
unset DXVK_ASYNC DXVK_HUD 2>/dev/null || true
}
main() {
gog_wine_check
if [[ ! -f "${BOTTLE_DIR}/.gog_galaxy_extract_installed" ]] \
&& ! find "${BOTTLE_DIR}/drive_c/Program Files"* -name 'GalaxyClient.exe' -print -quit 2>/dev/null | grep -q .; then
echo "Сначала: bash ${GOG_DIR}/install-galaxy-extract.sh"
exit 1
fi
local setup
setup="$(find_galaxy_setup || true)"
if [[ -z "${setup}" ]]; then
echo "Нет GalaxySetup.exe в Temp. Сначала web-stub:"
echo " bash ${GOG_DIR}/run-web-installer.sh /path/GOG_Galaxy_*.exe"
echo "Дождитесь скачивания ~277 MB, затем снова этот скрипт."
exit 1
fi
gog_kill_all_galaxy
export_wine_env_gog_setup
local win="Z:${setup#${BOTTLE_DIR}}"
win="${win//\//\\\\}"
# Wine path: C:\users\...
local cpath
cpath="$("${WINEPATH_BIN}" -w "${setup}" 2>/dev/null || true)"
if [[ -z "${cpath}" ]]; then
cpath="C:\\users\\eternal\\AppData\\Local\\Temp\\$(basename "$(dirname "${setup}")")\\GalaxySetup.exe"
fi
echo "Setup: ${setup}"
echo "Лог: ${LOG}"
: >"${LOG}"
"${WINE64}" "${cpath}" \
/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /FORCECLOSEAPPLICATIONS /SP- \
/lang=en_US /webinstaller "/product_id=${PRODUCT_ID}" "/game_name=${GAME_NAME}" \
>>"${LOG}" 2>&1 &
local pid=$!
local i
for i in $(seq 1 120); do
sleep 5
if ! kill -0 "${pid}" 2>/dev/null; then
break
fi
if grep -qi 'Install successful\|Successfully installed' "${LOG}" 2>/dev/null; then
break
fi
done
gog_kill_all_galaxy
local setup_log
setup_log="$(find "${BOTTLE_DIR}/drive_c/users" -name 'Setup Log*.txt' -print 2>/dev/null | sort | tail -1)"
if [[ -n "${setup_log}" ]]; then
echo "--- Inno Setup log (tail) ---"
tail -30 "${setup_log}"
fi
if grep -qi 'Install successful\|Successfully installed' "${setup_log:-/dev/null}" 2>/dev/null; then
echo "OK: установка завершена."
exit 0
fi
if grep -q 'Deinitializing Setup' "${setup_log:-/dev/null}" 2>/dev/null \
&& ! grep -q 'Access violation' "${setup_log:-/dev/null}" 2>/dev/null; then
echo "OK: Setup завершился без AV (проверьте игру в Galaxy)."
exit 0
fi
echo "Лог Wine:"
tail -25 "${LOG}"
exit 1
}
main "$@"

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# JA3 (GOG) через CrossOver + D3DMetal — D3D12 на Apple Silicon.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
# shellcheck source=gog-process.sh
source "${GOG_DIR}/gog-process.sh"
CX="${HOME}/Applications/CrossOver.app/Contents/SharedSupport/CrossOver"
[[ -d "/Applications/CrossOver.app" ]] && CX="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver"
CX_BOTTLE="JA3-GOG"
CX_DIR="${HOME}/Library/Application Support/CrossOver/Bottles/${CX_BOTTLE}"
[[ -d "${CX}" ]] || {
echo "❌ CrossOver не установлен: brew install --cask crossover"
exit 1
}
if [[ ! -f "${CX_DIR}/drive_c/GOG Games/Jagged Alliance 3/JA3.exe" ]]; then
bash "${GOG_DIR}/setup-ja3-crossover.sh"
fi
bash "${GOG_DIR}/emergency-stop.sh" >/dev/null 2>&1 || gog_kill_all
sleep 2
gog_swap_guard_ja3_launch "${JA3_SWAP_SOFT_MB:-1200}" "${JA3_SWAP_HARD_MB:-4096}" || exit 1
echo "Запуск JA3 (CrossOver D3DMetal; без Steam.app)…"
echo "Cmd+Tab → JA3. В графике: отключить heat haze."
echo "(лог «Steam … GOG» = GOG API-шим, не Steam client)"
# start /d — CWD с CommonLua/ (обязательно; --cx-app без /d → autorun.lua not found)
exec "${CX}/bin/wine" --bottle "${CX_BOTTLE}" start /d "C:\\GOG Games\\Jagged Alliance 3" \
"C:\\GOG Games\\Jagged Alliance 3\\JA3.exe" ${JA3_EXTRA_ARGS:--unpacked}

View File

@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# JA3 через Wine 7.7 + D3DMetal (Game Porting Toolkit). Канон для D3D12 на Apple Silicon.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
# shellcheck source=gog-process.sh
source "${GOG_DIR}/gog-process.sh"
GPTK_WINE="/Applications/Game Porting Toolkit.app/Contents/Resources/wine"
GPTK_WINE64="${GPTK_WINE}/bin/wine64"
JA3_WIN='C:\GOG Games\Jagged Alliance 3\JA3.exe'
MARKER="${GAMES_CACHE_DIR}/.ja3_gptk_prefix"
[[ -x "${GPTK_WINE64}" ]] || {
echo "❌ brew install --cask gcenx/wine/game-porting-toolkit"
exit 1
}
if [[ -f "${MARKER}" ]]; then
GPTK_PREFIX="$(cat "${MARKER}")"
else
GPTK_PREFIX="${JA3_GPTK_PREFIX:-${GAMES_CACHE_DIR}/ja3-gptk-prefix}"
fi
if [[ ! -f "${GPTK_PREFIX}/system.reg" ]]; then
bash "${GOG_DIR}/setup-ja3-gptk-prefix.sh"
GPTK_PREFIX="$(cat "${MARKER}" 2>/dev/null || echo "${GPTK_PREFIX}")"
fi
JA3_UNIX="${GPTK_PREFIX}/drive_c/GOG Games/Jagged Alliance 3"
[[ -f "${JA3_UNIX}/JA3.exe" ]] || JA3_UNIX="$(readlink "${JA3_UNIX}" 2>/dev/null || true)"
[[ -f "${JA3_UNIX}/JA3.exe" ]] || {
echo "❌ Нет JA3 в GPTK prefix — bash ${GOG_DIR}/setup-ja3-gptk-prefix.sh"
exit 1
}
bash "${GOG_DIR}/emergency-stop.sh" >/dev/null 2>&1 || gog_kill_all
sleep 2
if [[ "${JA3_IGNORE_SWAP:-0}" != "1" ]] && ! gog_swap_guard "${JA3_SWAP_MAX_MB:-800}"; then
exit 1
fi
export WINEPREFIX="${GPTK_PREFIX}"
export WINEARCH=win64
export WINED3DMETAL=1
export WINEMSYNC=1
export WINEDEBUG=-all
export MVK_CONFIG_LOG_LEVEL=0
export DYLD_LIBRARY_PATH="${GPTK_WINE}/lib:${DYLD_LIBRARY_PATH:-}"
export WINEDLLOVERRIDES="libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b"
echo "Запуск JA3 (GPTK Wine 7.7 + D3DMetal, prefix ${GPTK_PREFIX})…"
cd "${JA3_UNIX}"
exec "${GPTK_WINE64}" "${JA3_WIN}" ${JA3_EXTRA_ARGS:--unpacked}

103
scripts/games/gog/run-ja3.sh Executable file
View File

@@ -0,0 +1,103 @@
#!/usr/bin/env bash
# Запуск Jagged Alliance 3 (GOG) без Steam client.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
# shellcheck source=gog-process.sh
source "${GOG_DIR}/gog-process.sh"
JA3_WIN='C:\GOG Games\Jagged Alliance 3\JA3.exe'
JA3_UNIX="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3"
JA3_EXE="${JA3_UNIX}/JA3.exe"
GALAXY_SVC='C:\Program Files (x86)\GOG Galaxy\GalaxyClientService.exe'
if [[ ! -f "${JA3_EXE}" ]]; then
echo "❌ Нет ${JA3_EXE}"
echo " Установите: bash ${GOG_DIR}/install-ja3-innoextract.sh"
exit 1
fi
bash "${GOG_DIR}/emergency-stop.sh" >/dev/null 2>&1 || gog_kill_all
sleep 2
if [[ "${JA3_IGNORE_SWAP:-0}" == "1" ]]; then
echo "⚠️ JA3_IGNORE_SWAP=1 — swap $(gog_swap_used_mb 2>/dev/null || echo '?')MB"
elif ! gog_swap_guard_ja3_launch "${JA3_SWAP_SOFT_MB:-1200}" "${JA3_SWAP_HARD_MB:-4096}"; then
used="$(gog_swap_used_mb 2>/dev/null || echo '?')"
echo ""
echo "Swap ~${used}MB — bash ${GOG_DIR}/reset-ja3-crossover.sh или reboot Mac"
echo "Или: JA3_IGNORE_SWAP=1 bash ${GOG_DIR}/run-ja3.sh"
exit 1
fi
gog_assert_no_steam_client || exit 1
if ! ja3_gptk_available && ! ja3_bottle_uses_d3dmetal; then
if [[ -d "/Applications/Game Porting Toolkit.app" ]]; then
bash "${GOG_DIR}/install-gptk-whisky.sh" >/dev/null 2>&1 || true
bash "${GOG_DIR}/configure-ja3-d3dmetal.sh" >/dev/null 2>&1 || true
else
echo ""
echo "⚠️ JA3 нужен D3D12 (GPTK). Установите:"
echo " brew install --cask gcenx/wine/game-porting-toolkit"
echo " bash ${GOG_DIR}/install-gptk-whisky.sh && bash ${GOG_DIR}/configure-ja3-d3dmetal.sh"
echo ""
fi
fi
CX="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver"
if [[ ! -x "${CX}/bin/wine" ]]; then
:
elif [[ ! -f "${HOME}/Library/Application Support/CrossOver/Bottles/JA3-GOG/drive_c/GOG Games/Jagged Alliance 3/JA3.exe" ]]; then
echo ""
echo " JA3 (D3D12) на Mac: Whisky/Gcenx не поддерживает D3DMetal."
echo " brew install --cask crossover"
echo " bash ${GOG_DIR}/setup-ja3-crossover.sh && bash ${GOG_DIR}/run-ja3-crossover.sh"
echo ""
fi
# Whisky D3DMetal path — не работает для JA3/D3D12 на Gcenx (см. JA3_MAC_WITHOUT_GALAXY.md)
if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then
bash "${GOG_DIR}/prepare-ja3-d3dmetal.sh" >/dev/null 2>&1 || true
fi
bash "${GOG_DIR}/finish-ja3-layout.sh" >/dev/null 2>&1 || true
if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then
:
else
bash "${GOG_DIR}/force-ja3-d3d11.sh" >/dev/null 2>&1 || true
fi
export_wine_env_ja3
gog_wine_check
# Фоновый Galaxy service (Music/Matchmaking wrapper — не Steam)
if [[ -f "${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy/GalaxyClientService.exe" ]]; then
if ! pgrep -f 'GalaxyClientService' >/dev/null 2>&1; then
nohup "${WINE64}" "${GALAXY_SVC}" >>/tmp/ja3-galaxy-service.log 2>&1 &
sleep 2
fi
fi
if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then
echo "⚠️ Whisky D3DMetal не поддерживает JA3/D3D12 (Gcenx без D3DMetal hooks)."
echo " Используйте: bash ${GOG_DIR}/run-ja3-crossover.sh"
echo ""
fi
if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then
echo "Запуск JA3 (Whisky — эксперимент; D3D12 → CrossOver)…"
else
echo "Запуск JA3 (GOG, DXVK D3D11 fallback; без Steam.app)…"
fi
echo "Cmd+Tab → wine64. В графике: отключить heat haze."
echo "(лог «Steam … GOG» = GOG API-шим, не Steam client)"
if ja3_gptk_available || ja3_bottle_uses_d3dmetal; then
_ja3_args="${JA3_EXTRA_ARGS:--unpacked}"
export MTL_HUD_ENABLED="${MTL_HUD_ENABLED:-0}"
else
_ja3_args="${JA3_EXTRA_ARGS:--unpacked -graphicsapi d3d11}"
fi
# start /d — CWD = папка игры (CommonLua/, иначе «graphics subsystem»/autorun.lua из Whisky Run)
# innoextract не запускает Inno-постскрипты; -unpacked + CommonLua на диске обязательны
# shellcheck disable=SC2086
exec "${WINE64}" start /d "C:\\GOG Games\\Jagged Alliance 3" "${JA3_WIN}" ${_ja3_args}

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# GOG web-stub в отдельном GOG-bottle. Без Steam/HoMM.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
# shellcheck source=wine-fonts.sh
source "${GOG_DIR}/wine-fonts.sh"
GOG_SETUP_MARKER="${BOTTLE_DIR}/.gog_setup_v1"
LOG="/tmp/gog-web-installer-${BOTTLE_NAME}.log"
usage() {
cat <<EOF
Использование: $(basename "$0") /path/to/GOG_Galaxy_GameName.exe
Bottle: ${BOTTLE_NAME} (${BOTTLE_ID}) — только GOG, без Steam.
Один раз: bash ${GOG_DIR}/use-bottle.sh <UUID>
bash ${GOG_DIR}/setup-bottle.sh
bash ${GOG_DIR}/install-corefonts.sh
Лог: ${LOG}
EOF
}
gog_kill_installers() {
pkill -f 'GOG_Galaxy_' 2>/dev/null || true
pkill -f 'GalaxyWebInstaller' 2>/dev/null || true
sleep 1
}
main() {
local src="${1:-}"
if [[ -z "${src}" || "${src}" == "-h" || "${src}" == "--help" ]]; then
usage
exit 0
fi
[[ -f "${src}" ]] || { echo "Нет файла: ${src}"; exit 1; }
gog_wine_check
gog_disk_guard 5 || exit 1
if [[ ! -f "${BOTTLE_DIR}/.gog_gdiplus_installed" ]]; then
echo "⚠️ Нет gdiplus — один раз: bash ${GOG_DIR}/install-gdiplus.sh"
fi
if [[ ! -f "${BOTTLE_DIR}/.gog_corefonts_installed" ]]; then
echo "⚠️ Сначала: bash ${GOG_DIR}/install-corefonts.sh"
fi
[[ -f "${GOG_SETUP_MARKER}" ]] || bash "${GOG_DIR}/setup-bottle.sh"
gog_wine_ensure_fonts 0
local base dest_win
base="$(basename "${src}")"
dest_win="C:\\${base}"
cp -f "${src}" "${BOTTLE_DIR}/drive_c/${base}"
gog_kill_installers
export_wine_env_gog
echo "Bottle: ${BOTTLE_NAME}"
echo "Запуск: ${dest_win}"
echo "Лог: ${LOG}"
: >"${LOG}"
nohup "${WINE64}" "${dest_win}" >"${LOG}" 2>&1 &
local i alive=0
for i in 1 2 3 4 5 6; do
sleep 2
if pgrep -f "${base}" >/dev/null 2>&1; then
alive=1
break
fi
done
if [[ "${alive}" -eq 1 ]]; then
sleep 3
if grep -q 'FontFamily could not be found' "${LOG}" 2>/dev/null; then
echo "❌ FontFamily в логе — bash ${GOG_DIR}/install-gdiplus.sh && install-corefonts.sh"
tail -5 "${LOG}"
exit 1
fi
echo "OK: установщик запущен (Cmd+Tab → ${base})."
exit 0
fi
if grep -q 'FontFamily could not be found' "${LOG}" 2>/dev/null; then
echo "❌ FontFamilyNotFound:"
tail -8 "${LOG}"
exit 1
fi
echo "Процесс завершился:"
tail -20 "${LOG}"
exit 1
}
main "$@"

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Один раз: GOG-bottle — шрифты + сеть Wine. Без Steam/HoMM.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
# shellcheck source=wine-fonts.sh
source "${GOG_DIR}/wine-fonts.sh"
GOG_SETUP_VERSION="1"
GOG_SETUP_MARKER="${BOTTLE_DIR}/.gog_setup_v${GOG_SETUP_VERSION}"
main() {
local force=0 fonts_only=0
for arg in "$@"; do
case "${arg}" in
--force) force=1 ;;
--fonts-only) fonts_only=1 ;;
esac
done
gog_wine_check
if [[ "${fonts_only}" -eq 1 ]]; then
gog_wine_ensure_fonts 1
exit 0
fi
if [[ -f "${GOG_SETUP_MARKER}" && "${force}" -eq 0 ]]; then
echo "GOG bottle готов: ${GOG_SETUP_MARKER}"
echo " Шрифты: bash ${GOG_DIR}/setup-bottle.sh --fonts-only"
echo " Заново: bash ${GOG_DIR}/setup-bottle.sh --force"
exit 0
fi
echo "Подготовка GOG bottle «${BOTTLE_NAME}» (v${GOG_SETUP_VERSION})…"
gog_wine_ensure_fonts 1
export_wine_env_gog
"${WINE64}" reg add 'HKCU\Software\Wine\Network' /v DnsIPv6Enabled /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true
"${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \
/v DisableIPv6 /t REG_DWORD /d 1 /f >/dev/null 2>&1 || true
"${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \
/v ProxyEnable /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true
echo "Wine: DnsIPv6Enabled=0, ProxyEnable=0"
date -u +"%Y-%m-%dT%H:%M:%SZ" >"${GOG_SETUP_MARKER}"
echo ""
echo "OK. Дальше:"
echo " bash ${GOG_DIR}/install-gdiplus.sh # обязательно для .NET"
echo " bash ${GOG_DIR}/install-corefonts.sh"
echo " bash ${GOG_DIR}/run-web-installer.sh /path/GOG_Galaxy_*.exe"
}
main "$@"

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Bottle только для JA3 (offline GOG installer + игра). Без Galaxy CEF.
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
gog_swap_guard "${GOG_SWAP_MAX_MB:-400}" || exit 1
gog_disk_guard 10 || exit 1
if [[ ! -f "${GOG_DIR}/bottle.env" ]]; then
echo "Сначала: Whisky → New Bottle → bash ${GOG_DIR}/create-bottle.sh GOG-Install"
exit 1
fi
echo "=== Подготовка bottle для JA3 (без Galaxy) ==="
bash "${GOG_DIR}/setup-bottle.sh"
bash "${GOG_DIR}/install-dxvk.sh" 2>/dev/null || bash "${GAMES_DIR}/install-dxvk-macos.sh" 2>/dev/null || \
echo "⚠️ DXVK не установлен — JA3 может не стартовать (D3D11). Повторите install-dxvk."
echo ""
echo "OK. Дальше:"
echo " bash ${GOG_DIR}/install-ja3-offline.sh ~/Downloads/setup_jagged_alliance_3_*.exe"

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# CrossOver bottle JA3-GOG: D3D12 через D3DMetal (единственный подтверждённый путь на M1 Pro).
# Whisky/Gcenx Wine не содержит D3DMetal hooks (см. Gcenx/macOS_Wine_builds#97).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
CX_APP="/Applications/CrossOver.app"
CX="${CX_APP}/Contents/SharedSupport/CrossOver"
CX_BOTTLE="JA3-GOG"
CX_DIR="${HOME}/Library/Application Support/CrossOver/Bottles/${CX_BOTTLE}"
JA3_SRC="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3"
JA3_DEST="${CX_DIR}/drive_c/GOG Games/Jagged Alliance 3"
[[ -d "${CX_APP}" ]] || {
echo "❌ Нет CrossOver.app"
echo " brew install --cask crossover"
exit 1
}
[[ -f "${JA3_SRC}/JA3.exe" ]] || {
echo "❌ Нет JA3 в ${BOTTLE_NAME} — install-ja3-innoextract.sh"
exit 1
}
bash "${GOG_DIR}/finish-ja3-layout.sh" >/dev/null 2>&1 || true
if [[ ! -f "${CX_DIR}/system.reg" ]]; then
echo "Создание CrossOver bottle ${CX_BOTTLE} (win10_64)…"
"${CX}/bin/cxbottle" --create --bottle "${CX_BOTTLE}" --description "Jagged Alliance 3 GOG" --template win10_64
fi
CONF="${CX_DIR}/cxbottle.conf"
touch "${CONF}"
sed -i '' '/^"WINED3DMETAL"/d;/^"WINEDXVK"/d' "${CONF}" 2>/dev/null || true
{
echo '"WINED3DMETAL" = "1"'
echo '"WINEDXVK" = "0"'
} >> "${CONF}"
mkdir -p "${CX_DIR}/drive_c/GOG Games"
if [[ -L "${JA3_DEST}" ]] || [[ ! -d "${JA3_DEST}" ]]; then
rm -f "${JA3_DEST}"
echo "Клон игры в CrossOver bottle (APFS clonefile, ~0 доп. места)…"
cp -cR "${JA3_SRC}" "${JA3_DEST}"
elif [[ ! -f "${JA3_DEST}/CommonLua/Core/autorun.lua" ]]; then
echo "Обновление CommonLua в CrossOver bottle…"
rm -rf "${JA3_DEST}/CommonLua"
cp -R "${JA3_SRC}/CommonLua" "${JA3_DEST}/CommonLua"
fi
LS="${CX_DIR}/drive_c/users/crossover/AppData/Roaming/Jagged Alliance 3"
mkdir -p "${LS}"
if [[ ! -f "${LS}/LocalStorage.lua" ]]; then
cat >"${LS}/LocalStorage.lua" <<'EOF'
return {
Options = {
GraphicsApi = "d3d12",
VideoPreset = "Low",
},
}
EOF
fi
export WINEPREFIX="${CX_DIR}"
"${CX}/bin/wine" --bottle "${CX_BOTTLE}" reg add 'HKLM\Software\WOW6432Node\GOG.com\Games\1556263729' /v gameName /t REG_SZ /d 'Jagged Alliance 3' /f >/dev/null 2>&1 || true
"${CX}/bin/wine" --bottle "${CX_BOTTLE}" reg add 'HKLM\Software\WOW6432Node\GOG.com\Games\1556263729' /v exe /t REG_SZ /d 'JA3.exe' /f >/dev/null 2>&1 || true
"${CX}/bin/wine" --bottle "${CX_BOTTLE}" reg add 'HKLM\Software\WOW6432Node\GOG.com\Games\1556263729' /v path /t REG_SZ /d 'C:\GOG Games\Jagged Alliance 3\' /f >/dev/null 2>&1 || true
echo "OK: CrossOver ${CX_BOTTLE} + D3DMetal"
bash "${GOG_DIR}/fix-ja3-crossover-mouse.sh"
echo " Запуск: bash ${GOG_DIR}/run-ja3-crossover.sh"

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Отдельный WINEPREFIX под Wine 7.7 из Game Porting Toolkit (D3D12 + D3DMetal).
# Whisky Wine 11 не грузит GPTK dxgi/d3d12 PE — JA3 падает на «Failed creating render device».
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
GPTK_WINE="/Applications/Game Porting Toolkit.app/Contents/Resources/wine"
GPTK_WINE64="${GPTK_WINE}/bin/wine64"
JA3_SRC="${BOTTLE_DIR}/drive_c/GOG Games/Jagged Alliance 3"
GPTK_PREFIX="${JA3_GPTK_PREFIX:-${GAMES_CACHE_DIR}/ja3-gptk-prefix}"
[[ -x "${GPTK_WINE64}" ]] || {
echo "❌ brew install --cask gcenx/wine/game-porting-toolkit"
exit 1
}
[[ -f "${JA3_SRC}/JA3.exe" ]] || {
echo "❌ Нет JA3 в bottle ${BOTTLE_NAME} — install-ja3-innoextract.sh"
exit 1
}
export WINEARCH=win64
export WINEPREFIX="${GPTK_PREFIX}"
export WINED3DMETAL=1
export WINEMSYNC=1
export WINEDEBUG=-all
export DYLD_LIBRARY_PATH="${GPTK_WINE}/lib:${DYLD_LIBRARY_PATH:-}"
mkdir -p "${GPTK_PREFIX}"
if [[ ! -f "${GPTK_PREFIX}/system.reg" ]]; then
echo "Инициализация GPTK prefix: ${GPTK_PREFIX}"
"${GPTK_WINE64}" wineboot -i
fi
GOG_DRIVE="${GPTK_PREFIX}/drive_c/GOG Games"
JA3_LINK="${GOG_DRIVE}/Jagged Alliance 3"
mkdir -p "${GOG_DRIVE}"
if [[ -L "${JA3_LINK}" ]]; then
:
elif [[ -d "${JA3_LINK}" ]]; then
echo " JA3 уже в prefix (не symlink)"
else
ln -s "${JA3_SRC}" "${JA3_LINK}"
fi
# CommonLua + upscale cleanup (на исходной копии в GOG bottle)
bash "${GOG_DIR}/finish-ja3-layout.sh" >/dev/null 2>&1 || true
WINE64="${GPTK_WINE64}" BOTTLE_DIR="${GPTK_PREFIX}" export_wine_env_gog
export WINED3DMETAL=1
export WINEDLLOVERRIDES="libxess,xess,dxcompiler,dxil,nvngx_dlss,XeFX,XeFX_Loader=d;gdiplus=n,b"
GAME_ID='1556263729'
KEY="HKLM\\Software\\WOW6432Node\\GOG.com\\Games\\${GAME_ID}"
"${GPTK_WINE64}" reg add "${KEY}" /v gameName /t REG_SZ /d 'Jagged Alliance 3' /f >/dev/null
"${GPTK_WINE64}" reg add "${KEY}" /v exe /t REG_SZ /d 'JA3.exe' /f >/dev/null
"${GPTK_WINE64}" reg add "${KEY}" /v path /t REG_SZ /d 'C:\GOG Games\Jagged Alliance 3\' /f >/dev/null
echo "${GPTK_PREFIX}" >"${GAMES_CACHE_DIR}/.ja3_gptk_prefix"
echo "OK: GPTK prefix → ${GPTK_PREFIX}"
echo " Запуск: bash ${GOG_DIR}/run-ja3-gptk-wine.sh"

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Whisky Libraries Wine 11.0 → Gcenx wine-staging 11.9 + GPTK D3DMetal bridge (JA3 / D3D12).
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
GAMES_DIR="$(cd "${GOG_DIR}/.." && pwd)"
# shellcheck source=config.sh
source "${GOG_DIR}/config.sh"
CACHE="${GAMES_CACHE_DIR}"
TAR="${CACHE}/wine-staging-11.9-osx64.tar.xz"
URL="https://github.com/Gcenx/macOS_Wine_builds/releases/download/11.9/wine-staging-11.9-osx64.tar.xz"
WHISKY_WINE="${WHISKY_LIBS}/Libraries/Wine"
STAMP="${WHISKY_LIBS}/Libraries/.wine_gcenx_11.9"
[[ -f "${TAR}" ]] || curl -L --fail -o "${TAR}" "${URL}"
EXTRACT="${CACHE}/wine-staging-11.9-extract"
rm -rf "${EXTRACT}"
mkdir -p "${EXTRACT}"
tar -xJf "${TAR}" -C "${EXTRACT}"
SRC="${EXTRACT}/Wine Staging.app/Contents/Resources/wine"
[[ -d "${SRC}/bin" ]] || {
echo "❌ Неверная структура архива"
exit 1
}
if [[ ! -f "${STAMP}" ]]; then
bak="${WHISKY_WINE}.bak.$(date +%Y%m%d%H%M)"
echo "Бэкап Wine → ${bak}"
cp -a "${WHISKY_WINE}" "${bak}"
fi
rsync -a --delete \
--exclude 'lib/external/' \
"${SRC}/" "${WHISKY_WINE}/"
# Gcenx: wine64 → wine; Whisky-скрипты ожидают bin/wine64
if [[ -x "${WHISKY_WINE}/bin/wine" ]] && [[ ! -e "${WHISKY_WINE}/bin/wine64" ]]; then
ln -sf wine "${WHISKY_WINE}/bin/wine64"
fi
bash "${GOG_DIR}/install-gptk-whisky.sh"
touch "${STAMP}"
echo "OK: Whisky Wine → staging 11.9 + GPTK D3DMetal"
"${WHISKY_WINE}/bin/wine64" --version 2>&1 | head -1

20
scripts/games/gog/use-bottle.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
GOG_DIR="$(cd "$(dirname "$0")" && pwd)"
id="${1:-}"
name="${2:-GOG-Install}"
if [[ -z "${id}" ]]; then
echo "Использование: $(basename "$0") <BOTTLE_UUID> [имя]"
bash "${GOG_DIR}/list-bottles.sh"
exit 1
fi
if [[ "${id}" == "38412122-AAE1-4831-9F2E-6A0EB364B3F9" ]]; then
echo "❌ Это bottle HoMM-OldenEra (Steam/HoMM). Создайте новый bottle в Whisky."
exit 1
fi
cat >"${GOG_DIR}/bottle.env" <<EOF
BOTTLE_ID=${id}
BOTTLE_NAME=${name}
EOF
echo "OK: ${GOG_DIR}/bottle.env → ${name} (${id})"
echo "Дальше: bash ${GOG_DIR}/setup-bottle.sh"

85
scripts/games/gog/wine-fonts.sh Executable file
View File

@@ -0,0 +1,85 @@
# shellcheck shell=bash
# Шрифты для GOG web-installer (.NET). Только macOS + corefonts — без Steam.
gog_wine_install_fonts_mac() {
local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts"
mkdir -p "${fonts}"
for f in /System/Library/Fonts/Supplemental/{Arial,Tahoma,Verdana,"Times New Roman","Courier New","Segoe UI"}.ttf \
/System/Library/Fonts/Supplemental/Arial*.ttf \
/Library/Fonts/Arial*.ttf; do
[[ -f "${f}" ]] && cp -n "${f}" "${fonts}/" 2>/dev/null || true
done
# .NET GenericSansSerif → Microsoft Sans Serif / Arial
cp -n "${fonts}/Arial.ttf" "${fonts}/sans-serif.ttf" 2>/dev/null || true
cp -n "${fonts}/Arial.ttf" "${fonts}/arial.ttf" 2>/dev/null || true
cp -n "${fonts}/Arial.ttf" "${fonts}/micross.ttf" 2>/dev/null || true
cp -n "${fonts}/Arial.ttf" "${fonts}/Microsoft Sans Serif.ttf" 2>/dev/null || true
cp -n "${fonts}/Tahoma.ttf" "${fonts}/tahoma.ttf" 2>/dev/null || true
}
gog_wine_register_font_file() {
local reg_name="$1"
local ttf_file="$2"
export WINEPREFIX="${BOTTLE_DIR}"
"${WINE64}" reg add 'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Fonts' \
/v "${reg_name} (TrueType)" /t REG_SZ /d "${ttf_file}" /f >/dev/null 2>&1 || true
}
gog_wine_register_all_fonts_dir() {
local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts"
local f base reg
for f in "${fonts}"/*; do
[[ -f "${f}" ]] || continue
[[ "${f}" == *.ttf || "${f}" == *.TTF ]] || continue
base="$(basename "${f}")"
reg="${base%.*}"
gog_wine_register_font_file "${reg}" "${base}"
done
}
gog_wine_wineboot_fonts() {
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
"${WINE64}" wineboot -u 2>/dev/null || true
}
gog_wine_register_fonts_registry() {
export WINEPREFIX="${BOTTLE_DIR}"
local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts"
gog_wine_register_font_file 'Arial' 'arial.ttf'
gog_wine_register_font_file 'Tahoma' 'tahoma.ttf'
gog_wine_register_font_file 'Verdana' 'verdana.ttf'
gog_wine_register_font_file 'Times New Roman' 'Times New Roman.ttf'
gog_wine_register_font_file 'Courier New' 'Courier New.ttf'
gog_wine_register_font_file 'Microsoft Sans Serif' 'micross.ttf'
local sub='HKLM\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes'
"${WINE64}" reg add "${sub}" /v 'MS Shell Dlg' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${sub}" /v 'MS Shell Dlg 2' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${sub}" /v 'MS Sans Serif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${sub}" /v 'Segoe UI' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
local rep='HKCU\Software\Wine\Fonts\Replacements'
"${WINE64}" reg add "${rep}" /v '@@MS_SHELLDLG' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${rep}" /v 'MS Shell Dlg' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${rep}" /v 'Microsoft Sans Serif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${rep}" /v 'SansSerif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${sub}" /v 'SansSerif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true
gog_wine_register_all_fonts_dir
}
gog_wine_ensure_fonts() {
local rebuild="${1:-0}"
export_wine_env_gog
gog_wine_install_fonts_mac
gog_wine_register_fonts_registry
if [[ "${rebuild}" == "1" ]]; then
echo "wineboot -u (~3060 с)…"
gog_wine_wineboot_fonts
fi
local n
n="$(ls "${BOTTLE_DIR}/drive_c/windows/Fonts" 2>/dev/null | wc -l | tr -d ' ')"
echo "Шрифты (${BOTTLE_NAME}): ${n} файлов"
}

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Gcenx/DXVK-macOS — patched DX11 for Apple Silicon + MoltenVK (Whisky credits this fork).
# Vanilla DXVK 2.x and «D3DMetal» без Apple GPTK на зеркале frankea Libraries не поднимают DX11.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
DXVK_CACHE="${GAMES_CACHE_DIR}/dxvk-macos"
DXVK_TAR="${DXVK_CACHE}/dxvk-macOS-async.tar.gz"
DXVK_URL="https://github.com/Gcenx/DXVK-macOS/releases/download/v1.10.3-20230507-repack/dxvk-macOS-async-v1.10.3-20230507-repack.tar.gz"
DXVK_DIR="${DXVK_CACHE}/dxvk-macOS-async-v1.10.3-20230507-repack"
DXVK_X64="${DXVK_DIR}/x64"
WINE_LIB64="${WHISKY_LIBS}/Libraries/Wine/lib/wine/x86_64-windows"
SYS32="${BOTTLE_DIR}/drive_c/windows/system32"
cmd_wine_check() {
if [[ ! -x "${WINE64}" ]]; then
echo "Сначала: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh"
exit 1
fi
}
cmd_download() {
mkdir -p "${DXVK_CACHE}"
if [[ ! -f "${DXVK_TAR}" ]]; then
echo "==> Скачивание DXVK-macOS (~2.7 MB)..."
curl -L -o "${DXVK_TAR}" "${DXVK_URL}"
fi
tar -xzf "${DXVK_TAR}" -C "${DXVK_CACHE}"
}
cmd_install() {
if [[ ! -f "${DXVK_X64}/d3d11.dll" ]]; then
cmd_download
fi
if [[ ! -d "${SYS32}" ]]; then
echo "Нет Wine prefix (drive_c). Откройте bottle в Whisky один раз."
exit 1
fi
echo "==> Копирование d3d11/d3d10core в system32, dxgi — wine builtin..."
cp -f "${WINE_LIB64}/dxgi.dll" "${SYS32}/dxgi.dll"
cp -f "${DXVK_X64}/d3d11.dll" "${SYS32}/d3d11.dll"
cp -f "${DXVK_X64}/d3d10core.dll" "${SYS32}/d3d10core.dll"
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d11 /d native,b /f >/dev/null
"${WINE64}" reg add 'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d10core /d native,b /f >/dev/null
echo "OK: DXVK-macOS v1.10.3-async в bottle ${BOTTLE_NAME}"
}
cmd_wine_check
cmd_install

View File

@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# GOG Galaxy 2.0: скачать официальный установщик и поставить в bottle HoMM-OldenEra.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
SILENT="${GOG_INSTALL_SILENT:-0}"
if [[ ! -d "${BOTTLE_DIR}/drive_c" ]]; then
echo "Bottle ещё не инициализирован."
echo "1) open -a Whisky"
echo "2) Откройте bottle «${BOTTLE_NAME}»"
echo "3) Запустите этот скрипт снова"
open -a Whisky
exit 1
fi
if exe="$(homm_gog_exe)"; [[ -n "${exe}" ]]; then
echo "GOG Galaxy уже установлен: ${exe}"
echo "Запуск: bash ${SCRIPT_DIR}/start.sh gog"
exit 0
fi
mkdir -p "${GAMES_CACHE_DIR}"
homm_gog_setup_url() {
if [[ -n "${GOG_SETUP_URL:-}" ]]; then
echo "${GOG_SETUP_URL}"
return 0
fi
local json url
json="$(curl -fsSL "https://remote-config.gog.com/components/webinstaller?component_version=2.0.0")"
url="$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["content"]["windows"]["downloadLink"])' <<<"${json}" 2>/dev/null)" || true
if [[ -z "${url}" ]]; then
url="https://content-system.gog.com/open_link/download?path=/open/galaxy/client/setup_galaxy_2.0.100.1.exe"
fi
echo "${url}"
}
if [[ ! -f "${GOG_SETUP}" ]]; then
dl_url="$(homm_gog_setup_url)"
echo "Скачиваю полный установщик GOG Galaxy (~290 MB)…"
echo " ${dl_url}"
curl -L --fail --retry 3 -C - -o "${GOG_SETUP}" "${dl_url}"
fi
echo "Размер установщика: $(du -sh "${GOG_SETUP}" | awk '{print $1}')"
min_gb="${HOMM_DISK_MIN_GB:-3}"
min_kb=$((min_gb * 1024 * 1024))
if [[ "$(homm_disk_free_kb)" -lt "${min_kb}" ]]; then
echo "❌ Мало места на диске (<${min_gb} GB). GOG Galaxy ~300+ MB."
exit 1
fi
homm_gog_install_from_extract() {
local extract_dir="${GAMES_CACHE_DIR}/gog-galaxy-extract"
local gog_pf="${BOTTLE_DIR}/drive_c/Program Files (x86)/GOG Galaxy"
local gog_pd="${BOTTLE_DIR}/drive_c/ProgramData/GOG.com/Galaxy"
if ! command -v innoextract >/dev/null 2>&1; then
echo "Нужен innoextract: brew install innoextract"
return 1
fi
echo "Распаковка установщика (innoextract) — обход gdi32 в Wine-инсталляторе…"
rm -rf "${extract_dir}"
mkdir -p "${extract_dir}"
innoextract -e -d "${extract_dir}" "${GOG_SETUP}"
mkdir -p "${gog_pf}" "${gog_pd}"
cp -R "${extract_dir}/app/." "${gog_pf}/"
local pd_src
pd_src="$(find "${extract_dir}" -type d -path '*/GOG.com/Galaxy' 2>/dev/null | head -1)"
if [[ -n "${pd_src}" ]]; then
cp -R "${pd_src}/." "${gog_pd}/"
fi
homm_gog_exe >/dev/null
}
export_wine_env_steam
# Wine Inno Setup часто падает на gdi32 в /VERYSILENT; по умолчанию — распаковка.
if [[ "${GOG_INSTALL_WINE:-0}" == "1" && "${SILENT}" == "1" ]]; then
echo "Тихая установка через Wine (эксперимент)…"
cp -f "${GOG_SETUP}" "${BOTTLE_DIR}/drive_c/setup_galaxy.exe" 2>/dev/null || true
"${WINE64}" 'C:\\setup_galaxy.exe' /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /FORCECLOSEAPPLICATIONS /SP- || true
sleep 10
elif [[ "${SILENT}" == "1" ]]; then
homm_gog_install_from_extract || exit 1
else
echo "Запуск GUI-установщика GOG Galaxy в bottle «${BOTTLE_NAME}»…"
echo "Дождитесь завершения мастера установки, затем:"
echo " bash ${SCRIPT_DIR}/start.sh gog"
if command -v whisky >/dev/null 2>&1; then
whisky run "${BOTTLE_NAME}" "${GOG_SETUP}"
else
"${WINE64}" "${GOG_SETUP}"
fi
exit 0
fi
if exe="$(homm_gog_exe)"; [[ -n "${exe}" ]]; then
echo "OK: GOG Galaxy → ${exe}"
bash "${SCRIPT_DIR}/pin-gog-whisky.sh" 2>/dev/null || true
echo "Запуск: bash ${SCRIPT_DIR}/start.sh gog"
else
echo "GalaxyClient.exe не найден."
echo " GUI: GOG_INSTALL_SILENT=0 bash ${SCRIPT_DIR}/install-gog-galaxy.sh"
echo " Wine silent (часто ломается): GOG_INSTALL_WINE=1 GOG_INSTALL_SILENT=1 …"
exit 1
fi

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Steam в bottle без GUI-мастера (распаковка SteamSetup.exe).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
if [[ ! -f "${STEAM_SETUP}" ]]; then
echo "Скачиваю SteamSetup.exe..."
mkdir -p "${GAMES_CACHE_DIR}"
curl -L --fail -o "${STEAM_SETUP}" \
"https://cdn.fastly.steamstatic.com/client/installer/SteamSetup.exe"
fi
command -v 7z >/dev/null || { echo "Нужен: brew install p7zip"; exit 1; }
pkill -f wineserver 2>/dev/null || true
sleep 1
EXTRACT="/tmp/steam-extract-$$"
rm -rf "${EXTRACT}"
mkdir -p "${EXTRACT}"
7z x -o"${EXTRACT}" "${STEAM_SETUP}" -y >/dev/null
rm -rf "${STEAM_DIR}"
mkdir -p "${STEAM_DIR}"
cp -R "${EXTRACT}/Steam.exe" "${EXTRACT}/uninstall.exe" \
"${EXTRACT}/bin" "${EXTRACT}/public" "${STEAM_DIR}/"
rm -rf "${EXTRACT}"
echo "OK: Steam скопирован в bottle. Запуск: bash ${SCRIPT_DIR}/start.sh"

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Steam через GUI-установщик в Whisky (если extract не подошёл).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
if [[ ! -f "${STEAM_SETUP}" ]]; then
echo "Скачиваю SteamSetup.exe..."
mkdir -p "${GAMES_CACHE_DIR}"
curl -L --fail -o "${STEAM_SETUP}" \
"https://cdn.fastly.steamstatic.com/client/installer/SteamSetup.exe"
fi
if [[ ! -d "${BOTTLE_DIR}/drive_c" ]]; then
echo "Bottle ещё не инициализирован."
echo "1) open -a Whisky"
echo "2) Откройте bottle «${BOTTLE_NAME}» и дождитесь создания Windows-префикса"
echo "3) Запустите этот скрипт снова"
open -a Whisky
exit 1
fi
echo "Запуск установщика Steam в bottle «${BOTTLE_NAME}»..."
whisky run "${BOTTLE_NAME}" "${STEAM_SETUP}"

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Workaround: official data.getwhisky.app is dead (404). Mirror from frankea/Whisky.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
LIB_URL="https://github.com/frankea/Whisky/releases/download/v3.0.0/Libraries.tar.gz"
APP_SUPPORT="${WHISKY_LIBS}"
mkdir -p "${GAMES_CACHE_DIR}"
echo "==> Download Libraries.tar.gz (~314 MB) if missing..."
if [[ ! -f "${LIBRARIES_TAR}" ]] || [[ $(stat -f%z "${LIBRARIES_TAR}" 2>/dev/null || stat -c%s "${LIBRARIES_TAR}") -lt 100000000 ]]; then
curl -L --fail -o "${LIBRARIES_TAR}" "${LIB_URL}"
fi
echo "==> Extract to ${APP_SUPPORT}"
mkdir -p "${APP_SUPPORT}"
tar -xzf "${LIBRARIES_TAR}" -C "${APP_SUPPORT}"
echo "==> Done. WhiskyWine version:"
plutil -p "${APP_SUPPORT}/Libraries/WhiskyWineVersion.plist"
echo "Restart Whisky (Cmd+Q → open -a Whisky)."

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# → GOG bottle only (см. gog/README.md).
exec "$(cd "$(dirname "$0")" && pwd)/gog/install-corefonts.sh" "$@"

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Bottle создан в com.isaacmarovitz.Whisky — franke Whisky 3 его не видит → нет окна.
# Перенос bottle + Libraries symlink + whisky add (один раз).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
ISAAC_BOTTLE="${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles/${BOTTLE_ID}"
FRANKE_BOTTLE="${HOME}/Library/Containers/com.franke.Whisky/Bottles/${BOTTLE_ID}"
FRANKE_LIBS="${HOME}/Library/Application Support/com.franke.Whisky/Libraries"
ISAAC_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky/Libraries"
echo "=== Миграция bottle → franke Whisky 3 ==="
if [[ ! -d "${ISAAC_BOTTLE}/drive_c" && -d "${FRANKE_BOTTLE}/drive_c" ]]; then
echo "Bottle уже в franke: ${FRANKE_BOTTLE}"
elif [[ -d "${ISAAC_BOTTLE}/drive_c" ]]; then
mkdir -p "${HOME}/Library/Containers/com.franke.Whisky/Bottles"
if [[ -e "${FRANKE_BOTTLE}" ]]; then
echo "Удаляю битую ссылку/папку: ${FRANKE_BOTTLE}"
rm -rf "${FRANKE_BOTTLE}"
fi
echo "Перенос ~11 GB (13 мин на SSD)…"
mv "${ISAAC_BOTTLE}" "${FRANKE_BOTTLE}"
echo "OK: ${FRANKE_BOTTLE}"
else
echo "Не найден bottle: ${ISAAC_BOTTLE}"
exit 1
fi
if [[ ! -x "${FRANKE_LIBS}/Wine/bin/wine64" ]]; then
echo "WhiskyWine в franke Libraries нет — symlink на isaacmarovitz…"
rm -rf "${FRANKE_LIBS}"
ln -s "${ISAAC_LIBS}" "${FRANKE_LIBS}"
fi
if command -v whisky >/dev/null 2>&1; then
if ! whisky list 2>/dev/null | grep -q "${BOTTLE_NAME}"; then
whisky add "${FRANKE_BOTTLE}" || echo "⚠️ whisky add вручную: Whisky → Add → ${FRANKE_BOTTLE}"
fi
echo "OK. BottleVM.plist обновлён для Whisky.app."
fi
# Whisky.app читает BottleVM.plist
BOTTLE_VM="${HOME}/Library/Containers/com.franke.Whisky/BottleVM.plist"
if [[ -f "${BOTTLE_VM}" ]] && ! grep -q "${BOTTLE_ID}" "${BOTTLE_VM}" 2>/dev/null; then
echo "Добавьте bottle в Whisky вручную: Whisky → + → Add existing →"
echo " ${FRANKE_BOTTLE}"
fi

66
scripts/games/pin-gog-whisky.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Закрепить GOG Galaxy в Whisky (Metadata.plist pins + Program Settings), как steam.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
if ! homm_gog_exe >/dev/null; then
echo "GOG Galaxy не установлен: bash ${SCRIPT_DIR}/start.sh gog-install"
exit 1
fi
GOG_EXE_PATH="$(homm_gog_exe)"
METADATA="${BOTTLE_DIR}/Metadata.plist"
PROG_SETTINGS="${BOTTLE_DIR}/Program Settings/GalaxyClient.exe.plist"
# file URL как у steam (пробелы → %20)
GOG_URL="file://${BOTTLE_DIR}/drive_c/Program%20Files%20(x86)/GOG%20Galaxy/GalaxyClient.exe"
if grep -q '<string>gog</string>' "${METADATA}" 2>/dev/null; then
echo "Pin «gog» уже есть в ${METADATA}"
else
python3 <<PY
import plistlib
from pathlib import Path
meta = Path("${METADATA}")
with meta.open("rb") as f:
data = plistlib.load(f)
pins = data.setdefault("info", {}).setdefault("pins", [])
if any(p.get("name") == "gog" for p in pins):
print("pin exists")
else:
pins.append({
"name": "gog",
"removable": False,
"url": {"relative": "${GOG_URL}"},
})
with meta.open("wb") as f:
plistlib.dump(data, f)
print("pin added")
PY
echo "OK: pin «gog» в Metadata.plist"
fi
mkdir -p "${BOTTLE_DIR}/Program Settings"
# shellcheck disable=SC2206
cef="$(echo ${GOG_GALAXY_CEF_ARGS})"
python3 <<PY
import plistlib
from pathlib import Path
ps = Path("${PROG_SETTINGS}")
data = {
"arguments": "${cef}",
"environment": {},
"locale": "",
}
with ps.open("wb") as f:
plistlib.dump(data, f)
print("OK: ${PROG_SETTINGS}")
PY
echo ""
echo "Перезапустите Whisky (Cmd+Q → open -a Whisky) — на bottle появится иконка «gog»."
echo "Запуск: клик по pin или: bash ${SCRIPT_DIR}/start.sh gog"

View File

@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Удалить клиент Steam из Wine-bottle(ов). Не трогает steam_api64.dll в папках GOG-игр.
set -euo pipefail
GAMES_DIR="$(cd "$(dirname "$0")" && pwd)"
purge_one_bottle() {
local bottle_dir="$1" name="$2" wine64="$3" wine_lib="$4"
echo ""
echo "=== ${name} ==="
pkill -9 -f "${bottle_dir}.*[Ss]team" 2>/dev/null || true
pkill -9 -f "${bottle_dir}.*steam\.exe" 2>/dev/null || true
export WINEPREFIX="${bottle_dir}"
export DYLD_LIBRARY_PATH="${wine_lib}:${DYLD_LIBRARY_PATH:-}"
local wineserver="${wine64%/wine64}/wineserver"
[[ -x "${wineserver}" ]] && "${wineserver}" -k 2>/dev/null || true
sleep 1
local removed=0 d
for d in \
"${bottle_dir}/drive_c/Program Files (x86)/Steam" \
"${bottle_dir}/drive_c/Program Files/Steam" \
"${bottle_dir}/drive_c/Steam"; do
if [[ -d "${d}" ]]; then
echo " rm -rf ${d}"
rm -rf "${d}"
removed=1
fi
done
if [[ -x "${wine64}" ]]; then
"${wine64}" reg delete 'HKCU\Software\Valve' /f >/dev/null 2>&1 || true
"${wine64}" reg delete 'HKLM\Software\Valve' /f >/dev/null 2>&1 || true
fi
if [[ "${removed}" -eq 1 ]]; then
echo " OK: Steam client удалён"
else
echo " OK: Steam client не найден"
fi
}
purge_macos_steam() {
echo ""
echo "=== macOS Steam.app ==="
local app
for app in /Applications/Steam.app "${HOME}/Applications/Steam.app"; do
if [[ -d "${app}" ]]; then
echo " rm -rf ${app}"
rm -rf "${app}"
fi
done
if [[ -d "${HOME}/Library/Application Support/Steam" ]]; then
echo " rm -rf ~/Library/Application Support/Steam"
rm -rf "${HOME}/Library/Application Support/Steam"
fi
if ! [[ -d /Applications/Steam.app || -d "${HOME}/Applications/Steam.app" ]]; then
echo " Steam.app не установлен"
fi
}
main() {
echo "Удаление Steam (Wine + macOS), без steam_api64.dll в GOG Games/…"
# GOG-Install
if [[ -f "${GAMES_DIR}/gog/bottle.env" ]]; then
# shellcheck source=gog/config.sh
source "${GAMES_DIR}/gog/config.sh"
purge_one_bottle "${BOTTLE_DIR}" "${BOTTLE_NAME}" "${WINE64}" "${WINE_LIB}"
fi
# HoMM / общий config
# shellcheck source=config.sh
source "${GAMES_DIR}/config.sh"
purge_one_bottle "${BOTTLE_DIR}" "${BOTTLE_NAME}" "${WINE64}" "${WINE_LIB}"
purge_macos_steam
echo ""
echo "Готово."
}
main "$@"

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# → GOG-only pipeline (отдельный bottle, без Steam).
exec "$(cd "$(dirname "$0")" && pwd)/gog/run-web-installer.sh" "$@"

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# Legacy alias → start.sh
exec "$(dirname "$0")/start.sh" steam "$@"

186
scripts/games/run-wine-exe.sh Executable file
View File

@@ -0,0 +1,186 @@
#!/usr/bin/env bash
# Универсальный запуск .exe в Whisky-bottle. См. docs/games/WINE_APPS_LAUNCH_RUNBOOK.md
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
BOTTLE_NAME="${WINE_BOTTLE_NAME:-}"
BOTTLE_ID="${WINE_BOTTLE_ID:-}"
GOG_BOTTLE_MODE="${GOG_BOTTLE:-0}"
PROFILE="plain"
BACKGROUND=0
LOG=""
EXE_ARG=""
usage() {
cat <<'EOF'
Использование: run-wine-exe.sh [опции] <exe или путь Wine>
Опции:
--bottle NAME Имя bottle в Whisky (напр. HoMM-OldenEra)
--bottle-id UUID UUID bottle (приоритет над --bottle)
--gog-bottle Bottle из scripts/games/gog/bottle.env
--profile PROF plain | steam | gog | game | ja3 (по умолчанию plain)
--background nohup, не ждать
--log FILE лог при --background
-h, --help
Примеры:
run-wine-exe.sh --profile steam 'C:\Program Files (x86)\Steam\Steam.exe'
run-wine-exe.sh --gog-bottle --profile gog ~/Downloads/setup.exe
run-wine-exe.sh --bottle HoMM-OldenEra --profile game 'C:\...\game.exe'
Документация: docs/games/WINE_APPS_LAUNCH_RUNBOOK.md
EOF
}
resolve_bottle_dir() {
if [[ "${GOG_BOTTLE_MODE}" != "1" && -z "${BOTTLE_ID}" && -z "${BOTTLE_NAME}" ]]; then
# config.sh: HoMM-OldenEra по умолчанию
return 0
fi
if [[ "${GOG_BOTTLE_MODE}" == "1" ]]; then
local gog_cfg="${SCRIPT_DIR}/gog/bottle.env"
if [[ ! -f "${gog_cfg}" ]]; then
echo "❌ Нет gog/bottle.env — bash ${SCRIPT_DIR}/gog/use-bottle.sh <UUID>"
exit 1
fi
# shellcheck source=/dev/null
source "${gog_cfg}"
BOTTLE_ID="${BOTTLE_ID:?}"
BOTTLE_NAME="${BOTTLE_NAME:-GOG-Install}"
fi
if [[ -n "${BOTTLE_ID}" ]]; then
local _f="${HOME}/Library/Containers/com.franke.Whisky/Bottles/${BOTTLE_ID}"
local _i="${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles/${BOTTLE_ID}"
if [[ -d "${_f}/drive_c" ]]; then
BOTTLE_DIR="${_f}"
WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky"
elif [[ -d "${_i}/drive_c" ]]; then
BOTTLE_DIR="${_i}"
WHISKY_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky"
else
echo "❌ Bottle не найден: ${BOTTLE_ID}"
exit 1
fi
return 0
fi
if [[ -n "${BOTTLE_NAME}" ]]; then
local d
for d in "${HOME}/Library/Containers/com.franke.Whisky/Bottles"/* \
"${HOME}/Library/Containers/com.isaacmarovitz.Whisky/Bottles"/*; do
[[ -d "${d}/drive_c" ]] || continue
if [[ "$(basename "${d}")" == "${BOTTLE_NAME}" ]] || \
grep -q "BOTTLE_NAME=${BOTTLE_NAME}" "${d}/Metadata.plist" 2>/dev/null; then
BOTTLE_DIR="${d}"
if [[ "${d}" == *com.franke* ]]; then
WHISKY_LIBS="${HOME}/Library/Application Support/com.franke.Whisky"
else
WHISKY_LIBS="${HOME}/Library/Application Support/com.isaacmarovitz.Whisky"
fi
return 0
fi
done
echo "❌ Bottle по имени не найден: ${BOTTLE_NAME}"
exit 1
fi
# default: HoMM from config.sh (already sourced)
}
to_win_path() {
local arg="$1"
if [[ "${arg}" =~ ^[A-Za-z]:\\ ]]; then
echo "${arg}"
return 0
fi
if [[ ! -f "${arg}" ]]; then
echo "❌ Нет файла: ${arg}" >&2
exit 1
fi
local base dest
base="$(basename "${arg}")"
dest="${BOTTLE_DIR}/drive_c/${base}"
cp -f "${arg}" "${dest}"
echo "C:\\${base}"
}
apply_profile() {
case "${PROFILE}" in
plain)
export WINEPREFIX="${BOTTLE_DIR}"
export WINEMSYNC=1 WINEESYNC=1
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
unset WINEDLLOVERRIDES 2>/dev/null || true
;;
steam)
export_wine_env_steam
;;
gog)
if [[ -f "${SCRIPT_DIR}/gog/config.sh" ]]; then
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/gog/config.sh"
export_wine_env_gog
else
export_wine_env_steam
export WINEDLLOVERRIDES="gdiplus=n,b"
fi
;;
game)
export_wine_env_game
;;
ja3)
if [[ -f "${SCRIPT_DIR}/gog/config.sh" ]]; then
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/gog/config.sh"
export_wine_env_ja3
else
export_wine_env_game
fi
;;
*)
echo "❌ Неизвестный профиль: ${PROFILE}"
exit 1
;;
esac
export WINEDEBUG="${WINEDEBUG:--all}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--bottle) BOTTLE_NAME="$2"; shift 2 ;;
--bottle-id) BOTTLE_ID="$2"; shift 2 ;;
--gog-bottle) GOG_BOTTLE_MODE=1; shift ;;
--profile) PROFILE="$2"; shift 2 ;;
--background) BACKGROUND=1; shift ;;
--log) LOG="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
-*) echo "Неизвестная опция: $1"; usage; exit 1 ;;
*) EXE_ARG="$1"; shift ;;
esac
done
[[ -n "${EXE_ARG}" ]] || { usage; exit 1; }
resolve_bottle_dir
WINE64="${WHISKY_LIBS}/Libraries/Wine/bin/wine64"
WINE_LIB="${WHISKY_LIBS}/Libraries/Wine/lib"
[[ -x "${WINE64}" ]] || { echo "❌ Нет Wine: install-whiskywine-manual.sh"; exit 1; }
[[ -d "${BOTTLE_DIR}/drive_c" ]] || { echo "❌ Нет drive_c в ${BOTTLE_DIR}"; exit 1; }
WIN_EXE="$(to_win_path "${EXE_ARG}")"
apply_profile
echo "Bottle: ${BOTTLE_DIR##*/}"
echo "Профиль: ${PROFILE}"
echo "Запуск: ${WIN_EXE}"
if [[ "${BACKGROUND}" -eq 1 ]]; then
LOG="${LOG:-/tmp/wine-exe-$(basename "${BOTTLE_DIR}").log}"
: >"${LOG}"
nohup "${WINE64}" "${WIN_EXE}" >>"${LOG}" 2>&1 &
echo "Фон (PID $!), лог: ${LOG}"
exit 0
fi
exec "${WINE64}" "${WIN_EXE}"

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
exec "$(cd "$(dirname "$0")" && pwd)/gog/setup-bottle.sh" "$@"

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# HoMM / Steam bottle (HoMM-OldenEra): шрифты + сеть Wine для Steam UI.
# GOG web-stub: scripts/games/gog/ — отдельный bottle, без Steam.
# См. docs/games/WINE_MAC_RUNBOOK.md
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
if [[ -z "${BOTTLE_DIR:-}" ]]; then
echo "❌ config.sh не задал BOTTLE_DIR (файл пустой или повреждён?)"
exit 1
fi
if [[ ! -d "${BOTTLE_DIR}/drive_c" ]]; then
echo "❌ Нет bottle drive_c: ${BOTTLE_DIR}"
echo " Откройте Whisky → bottle «${BOTTLE_NAME:-HoMM-OldenEra}» и дождитесь создания префикса."
exit 1
fi
# shellcheck source=wine-fonts.sh
source "${SCRIPT_DIR}/wine-fonts.sh"
WINE_SETUP_VERSION="3"
WINE_SETUP_MARKER="${BOTTLE_DIR}/.wine_mac_setup_v${WINE_SETUP_VERSION}"
cmd_wine_check() {
if [[ ! -x "${WINE64}" ]]; then
echo "Нет WhiskyWine: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh"
exit 1
fi
}
cmd_install_fonts_winetricks() {
[[ "${WINE_SETUP_WINETRICKS:-0}" == "1" ]] || return 0
if ! command -v winetricks >/dev/null 2>&1; then
echo "winetricks: brew install winetricks, затем WINE_SETUP_WINETRICKS=1"
return 0
fi
export WINEPREFIX="${BOTTLE_DIR}"
export WINE="${WINE64}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
export WINEDEBUG=-all
echo "winetricks liberation (до 3 мин)…"
if command -v gtimeout >/dev/null 2>&1; then
gtimeout 180 winetricks -q liberation 2>&1 | tail -5 || true
elif command -v timeout >/dev/null 2>&1; then
timeout 180 winetricks -q liberation 2>&1 | tail -5 || true
else
winetricks -q liberation 2>&1 | tail -5 || true
fi
homm_wine_register_fonts_registry
}
cmd_wine_network() {
export WINEPREFIX="${BOTTLE_DIR}"
"${WINE64}" reg add 'HKCU\Software\Wine\Network' /v DnsIPv6Enabled /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true
"${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \
/v DisableIPv6 /t REG_DWORD /d 1 /f >/dev/null 2>&1 || true
"${WINE64}" reg add 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings' \
/v ProxyEnable /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true
echo "Wine: DnsIPv6Enabled=0, ProxyEnable=0"
}
main() {
local force=0 fonts_only=0
for arg in "$@"; do
case "${arg}" in
--force) force=1 ;;
--fonts-only) fonts_only=1 ;;
esac
done
cmd_wine_check
if [[ "${fonts_only}" -eq 1 ]]; then
homm_wine_ensure_fonts 1
exit 0
fi
if [[ -f "${WINE_SETUP_MARKER}" && "${force}" -eq 0 ]]; then
echo "Bottle подготовлен: ${WINE_SETUP_MARKER}"
echo "Обновить только шрифты: bash ${SCRIPT_DIR}/setup-wine-bottle.sh --fonts-only"
echo "Полный повтор: bash ${SCRIPT_DIR}/setup-wine-bottle.sh --force"
exit 0
fi
echo "Подготовка bottle «${BOTTLE_NAME}» (v${WINE_SETUP_VERSION})…"
homm_wine_ensure_fonts 1
cmd_install_fonts_winetricks
homm_wine_ensure_fonts 0
cmd_wine_network
date -u +"%Y-%m-%dT%H:%M:%SZ" >"${WINE_SETUP_MARKER}"
echo ""
echo "OK. Steam/HoMM: bash ${SCRIPT_DIR}/start.sh steam-ui"
echo "GOG (другой bottle): bash ${SCRIPT_DIR}/gog/README.md"
}
main "$@"

281
scripts/games/start.sh Executable file
View File

@@ -0,0 +1,281 @@
#!/usr/bin/env bash
# HoMM Olden Era / Steam / GOG в bottle Whisky. См. docs/games/WINE_APPS_LAUNCH_RUNBOOK.md
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=config.sh
source "${SCRIPT_DIR}/config.sh"
HOMM_DISK_MIN_GB="${HOMM_DISK_MIN_GB:-3}"
HOMM_DISK_WARN_PCT="${HOMM_DISK_WARN_PCT:-10}"
usage() {
cat <<EOF
Использование: $(basename "$0") <команда>
play — игра (DXVK + Unity windowed; Steam Logged On)
game — то же, явный запуск exe
steam-ui — Steam с окном (CEF + шрифты)
steam — Steam -silent (без UI)
reset — wineboot -k, убить Steam/GOG/игру в bottle
dxvk — установить DXVK-macOS в bottle
disk — свободное место на диске
saves — открыть папку сейвов в Finder
fix-steam-ui — шрифты + dwrite для пустого текста Steam
fix-gog-auth — сеть Wine для GOG login
diagnose — общая диагностика (mouse)
diagnose-steam-ui
diagnose-saves
diagnose-gog-auth
input — WineBus SDL (мышь/геймпад)
gog-install — GOG Galaxy 2.0 в bottle
gog — запустить GOG Galaxy
gog-restart — убить GOG и запустить снова
gog-bottle / gog-use-bottle / gog-setup / gog-corefonts / gog-game-install / gog-reset
— GOG в отдельном bottle (см. scripts/games/gog/README.md)
wine-setup — HoMM/Steam bottle (не GOG)
whisky — открыть Whisky.app
help — эта справка (-h / --help)
EOF
}
cmd_wine_check() {
if [[ ! -x "${WINE64}" ]]; then
echo "Нет WhiskyWine: bash ${SCRIPT_DIR}/install-whiskywine-manual.sh"
exit 1
fi
}
cmd_disk_guard() {
local min_kb free_kb pct
min_kb=$((HOMM_DISK_MIN_GB * 1024 * 1024))
free_kb="$(homm_disk_free_kb)"
pct="$(homm_disk_free_pct)"
if [[ "${free_kb}" -lt "${min_kb}" ]]; then
echo "❌ Мало места (<${HOMM_DISK_MIN_GB} GB свободно на Data)."
return 1
fi
if [[ "${pct}" -lt "${HOMM_DISK_WARN_PCT}" ]]; then
echo "⚠️ Диск заполнен ~${pct}% свободно — риск зависаний CEF/Wine."
fi
return 0
}
cmd_disk() {
df -h "$(homm_data_volume_path)" | tail -1
echo "Свободно: $(homm_disk_free_pct)% ($(homm_disk_free_kb) KB)"
}
homm_steam_cef_args_array() {
local _IFS=$IFS
IFS=' '
# shellcheck disable=SC2206
local -a _a=(${HOMM_STEAM_CEF_ARGS})
IFS=$_IFS
printf '%s\n' "${_a[@]}"
}
homm_unity_args_array() {
local -a args=()
local w h
if [[ "${HOMM_UNITY_WINDOWED:-1}" == "1" ]]; then
IFS='x' read -r w h <<< "${HOMM_VDESKTOP:-1920x1080}"
args+=(-screen-fullscreen 0 -screen-width="${w}" -screen-height="${h}")
fi
if [[ -n "${HOMM_UNITY_EXTRA:-}" ]]; then
local _IFS=$IFS
IFS=' '
# shellcheck disable=SC2206
local -a extra=(${HOMM_UNITY_EXTRA})
IFS=$_IFS
args+=("${extra[@]}")
fi
if [[ ${#args[@]} -eq 0 ]]; then
return 0
fi
printf '%s\n' "${args[@]}"
}
cmd_reset() {
cmd_wine_check
export_wine_env
echo "wineboot -k…"
"${WINE64}" wineboot -k 2>/dev/null || true
pkill -f 'HeroesOldenEra\.exe' 2>/dev/null || true
pkill -f 'Steam\.exe' 2>/dev/null || true
pkill -f 'steamwebhelper' 2>/dev/null || true
pkill -f 'SteamService' 2>/dev/null || true
pkill -f 'GalaxyClient' 2>/dev/null || true
pkill -f 'GOG_Galaxy' 2>/dev/null || true
pkill -f 'GalaxyWebInstaller' 2>/dev/null || true
pkill -f 'explorer.exe /desktop' 2>/dev/null || true
sleep 1
echo "OK: процессы bottle остановлены."
}
cmd_steam_ui() {
local -a cef_args=()
cmd_wine_check
cmd_disk_guard || exit 1
homm_kill_mac_steam || true
homm_warn_mac_steam || true
export_wine_env_steam
mapfile -t cef_args < <(homm_steam_cef_args_array)
echo "Запуск Steam UI (Cmd+Tab → wine64 / Steam)…"
if [[ "${HOMM_STEAM_VDESKTOP:-0}" == "1" ]]; then
local w h
IFS='x' read -r w h <<< "${HOMM_VDESKTOP:-1920x1080}"
exec "${WINE64}" explorer "/desktop=SteamUI,${w}x${h}" "${STEAM_EXE}" "${cef_args[@]}"
else
exec "${WINE64}" "${STEAM_EXE}" "${cef_args[@]}"
fi
}
cmd_steam() {
cmd_wine_check
export_wine_env_steam
echo "Steam -silent…"
exec "${WINE64}" "${STEAM_EXE}" -silent
}
cmd_game() {
local -a unity=()
cmd_wine_check
cmd_disk_guard || exit 1
if [[ ! -f "${HOMM_EXE}" ]]; then
echo "Нет ${HOMM_EXE} — установите Olden Era в Steam UI."
exit 1
fi
if [[ "${HOMM_GAME_FORCE:-0}" != "1" ]] && ! homm_steam_ready_for_game; then
echo "❌ Steam не Logged On или не запущен."
echo " bash ${SCRIPT_DIR}/start.sh steam-ui → login → play"
exit 1
fi
mapfile -t unity < <(homm_unity_args_array)
export_wine_env_game
echo "Запуск HoMM (DXVK + windowed)…"
if [[ "${HOMM_USE_WINEVDESKTOP:-1}" == "1" && "${HOMM_LAUNCH_DIRECT:-0}" != "1" ]]; then
local w h
IFS='x' read -r w h <<< "${HOMM_VDESKTOP:-1920x1080}"
exec "${WINE64}" explorer "/desktop=HoMM,${w}x${h}" "${HOMM_EXE}" "${unity[@]}"
else
exec "${WINE64}" "${HOMM_EXE}" "${unity[@]}"
fi
}
cmd_play() {
if homm_steam_ready_for_game; then
cmd_game
else
homm_warn_mac_steam || true
echo "Нужен Steam Logged On. Сначала steam-ui, затем снова play."
if ! homm_wine_steam_running; then
cmd_steam_ui
else
exit 1
fi
fi
}
cmd_saves() {
local sp
sp="$(find "${BOTTLE_DIR}/drive_c/users" -type d -path '*/Unfrozen/HeroesOldenEra/users/*/saves/singleplayer' 2>/dev/null | head -1)"
if [[ -z "${sp}" ]]; then
echo "Папка сейвов не найдена — запустите игру хотя бы раз."
exit 1
fi
open "${sp}"
echo "${sp}"
}
cmd_gog() {
local gog_exe win_exe
local -a cef_args=()
cmd_wine_check
cmd_disk_guard || exit 1
if ! gog_exe="$(homm_gog_exe)"; then
echo "GOG не установлен: bash ${SCRIPT_DIR}/start.sh gog-install"
exit 1
fi
if homm_wine_gog_running; then
echo "GOG уже запущен (Cmd+Tab → GalaxyClient)."
exit 0
fi
homm_kill_mac_steam || true
export_wine_env_gog
mapfile -t cef_args < <(homm_gog_cef_args_array)
if [[ -x "${WINEPATH_BIN}" ]]; then
win_exe="$("${WINEPATH_BIN}" -w "${gog_exe}")"
else
win_exe='C:\Program Files (x86)\GOG Galaxy\GalaxyClient.exe'
fi
echo "Запуск GOG Galaxy…"
nohup "${WINE64}" "${win_exe}" "${cef_args[@]}" >/tmp/gog-launch.log 2>&1 &
sleep 3
if homm_wine_gog_running; then
echo "OK: Cmd+Tab → GalaxyClient"
else
echo "Не стартовал — tail /tmp/gog-launch.log"
tail -10 /tmp/gog-launch.log 2>/dev/null || true
exit 1
fi
}
cmd_gog_restart() {
pkill -f 'GalaxyClient' 2>/dev/null || true
pkill -f 'GOG Galaxy' 2>/dev/null || true
sleep 2
cmd_gog
}
cmd_whisky() {
open -a Whisky 2>/dev/null || open -a "Whisky.app" 2>/dev/null || {
echo "Установите Whisky (franke) или откройте bottle вручную."
exit 1
}
}
main() {
local cmd="${1:-steam}"
shift || true
case "${cmd}" in
-h|--help|help) usage; exit 0 ;;
play) cmd_play "$@" ;;
game) cmd_game "$@" ;;
steam-ui) cmd_steam_ui "$@" ;;
steam) cmd_steam "$@" ;;
reset) cmd_reset "$@" ;;
dxvk) exec bash "${SCRIPT_DIR}/install-dxvk-macos.sh" "$@" ;;
disk) cmd_disk "$@" ;;
saves) cmd_saves "$@" ;;
fix-steam-ui) exec bash "${SCRIPT_DIR}/fix-homm-steam-ui.sh" "$@" ;;
fix-gog-auth) exec bash "${SCRIPT_DIR}/fix-gog-auth.sh" "$@" ;;
diagnose) exec bash "${SCRIPT_DIR}/diagnose-homm-mouse.sh" "$@" ;;
diagnose-steam-ui) exec bash "${SCRIPT_DIR}/diagnose-homm-steam-ui.sh" "$@" ;;
diagnose-saves) exec bash "${SCRIPT_DIR}/diagnose-homm-saves.sh" "$@" ;;
diagnose-gog-auth) exec bash "${SCRIPT_DIR}/diagnose-gog-auth.sh" "$@" ;;
input) exec bash "${SCRIPT_DIR}/fix-homm-wine-input.sh" "$@" ;;
gog-install) exec bash "${SCRIPT_DIR}/install-gog-galaxy.sh" "$@" ;;
gog) cmd_gog "$@" ;;
gog-restart) cmd_gog_restart "$@" ;;
gog-bottle) exec bash "${SCRIPT_DIR}/gog/list-bottles.sh" "$@" ;;
gog-use-bottle) shift; exec bash "${SCRIPT_DIR}/gog/use-bottle.sh" "$@" ;;
gog-setup) exec bash "${SCRIPT_DIR}/gog/setup-bottle.sh" "$@" ;;
gog-install-all) shift; exec bash "${SCRIPT_DIR}/gog/install-all.sh" "$@" ;;
gog-gdiplus) exec bash "${SCRIPT_DIR}/gog/install-gdiplus.sh" "$@" ;;
gog-create-bottle) exec bash "${SCRIPT_DIR}/gog/create-bottle.sh" "$@" ;;
gog-corefonts) exec bash "${SCRIPT_DIR}/gog/install-corefonts.sh" "$@" ;;
gog-game-install) exec bash "${SCRIPT_DIR}/gog/run-web-installer.sh" "$@" ;;
gog-reset) exec bash "${SCRIPT_DIR}/gog/reset.sh" "$@" ;;
wine-setup) exec bash "${SCRIPT_DIR}/setup-wine-bottle.sh" "$@" ;;
wine-corefonts) echo "Для GOG: bash ${SCRIPT_DIR}/gog/install-corefonts.sh"; exit 1 ;;
whisky) cmd_whisky "$@" ;;
"") cmd_steam "$@" ;;
*)
echo "Неизвестная команда: ${cmd}"
usage
exit 1
;;
esac
}
main "$@"

104
scripts/games/wine-fonts.sh Normal file
View File

@@ -0,0 +1,104 @@
# shellcheck shell=bash
# Шрифты для HoMM/Steam bottle. GOG: scripts/games/gog/wine-fonts.sh (без Steam).
homm_wine_install_fonts_mac() {
local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts"
local steam="${STEAM_DIR}"
mkdir -p "${fonts}"
for f in /System/Library/Fonts/Supplemental/{Arial,Tahoma,Verdana,"Times New Roman","Courier New"}.ttf; do
[[ -f "${f}" ]] && cp -n "${f}" "${fonts}/" 2>/dev/null || true
done
cp -n "${fonts}/Arial.ttf" "${fonts}/arial.ttf" 2>/dev/null || true
cp -n "${fonts}/Arial.ttf" "${fonts}/micross.ttf" 2>/dev/null || true
cp -n "${fonts}/Arial.ttf" "${fonts}/Microsoft Sans Serif.ttf" 2>/dev/null || true
cp -n "${fonts}/Tahoma.ttf" "${fonts}/tahoma.ttf" 2>/dev/null || true
[[ -f "${fonts}/liberationsans-regular.ttf" ]] || {
if command -v winetricks >/dev/null 2>&1 && [[ "${WINE_SETUP_WINETRICKS:-0}" == "1" ]]; then
:
fi
}
if [[ "${WINE_FONTS_USE_STEAM:-1}" == "1" && -d "${steam}" ]]; then
cp -n "${steam}/resource/fonts/marlett.ttf" "${fonts}/" 2>/dev/null || true
cp -n "${steam}/clientui/fonts/"*.ttf "${fonts}/" 2>/dev/null || true
fi
}
homm_wine_register_font_file() {
local reg_name="$1"
local ttf_file="$2"
export WINEPREFIX="${BOTTLE_DIR}"
"${WINE64}" reg add 'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Fonts' \
/v "${reg_name} (TrueType)" /t REG_SZ /d "${ttf_file}" /f >/dev/null 2>&1 || true
}
homm_wine_register_all_fonts_dir() {
local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts"
local f base reg
for f in "${fonts}"/*; do
[[ -f "${f}" ]] || continue
[[ "${f}" == *.ttf || "${f}" == *.TTF ]] || continue
base="$(basename "${f}")"
reg="${base%.*}"
homm_wine_register_font_file "${reg}" "${base}"
done
}
homm_wine_wineboot_fonts() {
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
"${WINE64}" wineboot -u 2>/dev/null || true
}
homm_wine_register_fonts_registry() {
export WINEPREFIX="${BOTTLE_DIR}"
local fonts="${BOTTLE_DIR}/drive_c/windows/Fonts"
homm_wine_register_font_file 'Arial' 'arial.ttf'
homm_wine_register_font_file 'Tahoma' 'tahoma.ttf'
homm_wine_register_font_file 'Verdana' 'verdana.ttf'
homm_wine_register_font_file 'Times New Roman' 'Times New Roman.ttf'
homm_wine_register_font_file 'Courier New' 'Courier New.ttf'
homm_wine_register_font_file 'Microsoft Sans Serif' 'micross.ttf'
[[ -f "${fonts}/liberationsans-regular.ttf" ]] && \
homm_wine_register_font_file 'Liberation Sans' 'liberationsans-regular.ttf'
[[ -f "${fonts}/liberationsans-bold.ttf" ]] && \
homm_wine_register_font_file 'Liberation Sans Bold' 'liberationsans-bold.ttf'
# Substitutes — .NET GenericSansSerif / MS Shell Dlg
local sub='HKLM\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes'
"${WINE64}" reg add "${sub}" /v 'MS Shell Dlg' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${sub}" /v 'MS Shell Dlg 2' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${sub}" /v 'MS Sans Serif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${sub}" /v 'Segoe UI' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${sub}" /v 'MessageBox' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
# Wine-internal mapping (libgdiplus / GenericSansSerif)
local rep='HKCU\Software\Wine\Fonts\Replacements'
"${WINE64}" reg add "${rep}" /v '@@MS_SHELLDLG' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${rep}" /v 'MS Shell Dlg' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${rep}" /v 'MS Shell Dlg 2' /t REG_SZ /d 'Tahoma' /f >/dev/null 2>&1 || true
"${WINE64}" reg add "${rep}" /v 'Microsoft Sans Serif' /t REG_SZ /d 'Arial' /f >/dev/null 2>&1 || true
"${WINE64}" reg add 'HKCU\Software\Valve\Steam' /v DWriteEnable /t REG_DWORD /d 0 /f >/dev/null 2>&1 || true
homm_wine_register_all_fonts_dir
}
homm_wine_ensure_fonts() {
local rebuild="${1:-0}"
if [[ ! -x "${WINE64}" ]]; then
echo "Нет Wine64: ${WINE64}"
return 1
fi
export WINEPREFIX="${BOTTLE_DIR}"
export DYLD_LIBRARY_PATH="${WINE_LIB}:${DYLD_LIBRARY_PATH:-}"
homm_wine_install_fonts_mac
homm_wine_register_fonts_registry
if [[ "${rebuild}" == "1" ]]; then
echo "wineboot -u (перечитать шрифты, ~3060 с)…"
homm_wine_wineboot_fonts
fi
local n
n="$(ls "${BOTTLE_DIR}/drive_c/windows/Fonts" 2>/dev/null | wc -l | tr -d ' ')"
echo "Шрифты Wine: ${n} файлов, реестр FontSubstitutes + Wine\\Fonts\\Replacements"
}

3
scripts/vpn/exit-via-local.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# См. docs/connectivity/vpn/SSH_EXIT_MODES.md
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssh-exit-mode.sh" local "$@"

3
scripts/vpn/exit-via-ru-do.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# См. docs/connectivity/vpn/SSH_EXIT_MODES.md
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssh-exit-mode.sh" ru-do "$@"

3
scripts/vpn/exit-via-ru.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# См. docs/connectivity/vpn/SSH_EXIT_MODES.md
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssh-exit-mode.sh" ru "$@"

139
scripts/vpn/ssh-exit-mode.sh Executable file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env bash
# Переключение режима выхода в интернет через SSH SOCKS (macOS / Linux).
# См. docs/connectivity/vpn/SSH_EXIT_MODES.md
set -e
SSH_EXIT_MAIN_PORT="${SSH_EXIT_MAIN_PORT:-10809}"
SSH_EXIT_STOP_PORTS="${SSH_EXIT_STOP_PORTS:-10809 1081 1082}"
SSH_EXIT_RU_HOST="${SSH_EXIT_RU_HOST:-hsites-ahau}"
SSH_SSH_STABLE_OPTS="-o TCPKeepAlive=yes -o IPQoS=throughput -o ServerAliveInterval=60 -o ServerAliveCountMax=4"
SSH_SSH_RU_OPTS="-o TCPKeepAlive=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=6"
usage() {
cat <<EOF
Usage: $(basename "$0") COMMAND
local | off | stop Остановить SSH SOCKS на портах: $SSH_EXIT_STOP_PORTS
(трафик без SOCKS — ваш локальный публичный IP).
ru SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → выход с RU ($SSH_EXIT_RU_HOST).
ru-do SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT → Mac → RU → DO → интернет
(telegram-socks-via-do). Без RU — ошибка (без прямого Mac→DO).
status Кто слушает порты из SSH_EXIT_STOP_PORTS.
verify Показать IP без SOCKS и через 127.0.0.1:$SSH_EXIT_MAIN_PORT.
Env:
SSH_EXIT_MAIN_PORT Порт SOCKS для ru / ru-do (по умолчанию 10809).
SSH_EXIT_STOP_PORTS Порты для команды local (пробелы).
SSH_EXIT_RU_HOST Хост только-RU (по умолчанию hsites-ahau).
Требуется ~/.ssh/config: $SSH_EXIT_RU_HOST, telegram-socks-via-do (шаблон — docs/connectivity/PROXY_GUIDE.md).
EOF
}
stop_dynamic_tunnels() {
local p
for p in $SSH_EXIT_STOP_PORTS; do
pkill -f "ssh.*-D ${p} " 2>/dev/null || true
pkill -f "ssh.*-D ${p}\$" 2>/dev/null || true
done
sleep 0.4
for p in $SSH_EXIT_STOP_PORTS; do
if command -v lsof >/dev/null 2>&1; then
# shellcheck disable=SC2046
lsof -ti ":$p" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true
fi
done
}
cmd_local() {
stop_dynamic_tunnels
echo "SSH SOCKS на портах ($SSH_EXIT_STOP_PORTS) остановлены. Выход в интернет — с локального IP (если в приложениях отключён SOCKS-прокси)."
}
ensure_port_free() {
if command -v lsof >/dev/null 2>&1; then
if lsof -i :"$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
echo "Порт $SSH_EXIT_MAIN_PORT занят — освобождаю..."
pkill -f "ssh.*-D ${SSH_EXIT_MAIN_PORT} " 2>/dev/null || true
sleep 0.4
# shellcheck disable=SC2046
lsof -ti ":$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true
sleep 0.3
fi
fi
if lsof -i :"$SSH_EXIT_MAIN_PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
echo "Ошибка: порт $SSH_EXIT_MAIN_PORT всё ещё занят. Закройте процесс вручную или: $0 local"
exit 1
fi
}
cmd_ru() {
ensure_port_free
# shellcheck disable=SC2086
ssh -D "$SSH_EXIT_MAIN_PORT" -f -N $SSH_SSH_RU_OPTS "$SSH_EXIT_RU_HOST"
echo "SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT$SSH_EXIT_RU_HOST (выход с IP RU-прокси)."
}
cmd_ru_do() {
ensure_port_free
if ! ssh -o BatchMode=yes -o ConnectTimeout=6 -o ConnectionAttempts=1 "$SSH_EXIT_RU_HOST" "exit 0" 2>/dev/null; then
echo "Ошибка: $SSH_EXIT_RU_HOST недоступен. Режим ru-do только Mac→RU→DO; прямой Mac→DO не используется." >&2
exit 1
fi
# shellcheck disable=SC2086
ssh -D "$SSH_EXIT_MAIN_PORT" -f -N $SSH_SSH_STABLE_OPTS telegram-socks-via-do
echo "SOCKS 127.0.0.1:$SSH_EXIT_MAIN_PORT$SSH_EXIT_RU_HOST → DO (telegram-socks-via-do). Снаружи виден IP DO."
}
cmd_status() {
local p any=0
if ! command -v lsof >/dev/null 2>&1; then
echo "lsof не найден — статус портов недоступен."
exit 1
fi
for p in $SSH_EXIT_STOP_PORTS; do
if lsof -i :"$p" -sTCP:LISTEN -t >/dev/null 2>&1; then
echo "--- :$p ---"
lsof -i :"$p" -sTCP:LISTEN
any=1
fi
done
if [ "$any" = 0 ]; then
echo "Ни один из портов не слушает: $SSH_EXIT_STOP_PORTS"
fi
}
cmd_verify() {
echo "Без прокси (локальный выход):"
curl -sS --max-time 12 https://ipinfo.io/ip 2>/dev/null || echo "(ошибка curl)"
echo ""
echo "Через SOCKS socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT:"
curl -sS --max-time 12 -x "socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT" https://ipinfo.io/ip 2>/dev/null || echo "(нет ответа — туннель не поднят или порт другой)"
echo ""
echo "Кратко через SOCKS (ip + страна):"
curl -sS --max-time 12 -x "socks5h://127.0.0.1:$SSH_EXIT_MAIN_PORT" https://ipinfo.io/json 2>/dev/null \
| sed -n 's/.*"ip": *"\([^"]*\)".*/IP: \1/p; s/.*"country": *"\([^"]*\)".*/country: \1/p' 2>/dev/null | head -4 || true
}
case "${1:-}" in
local|off|stop) cmd_local ;;
ru) cmd_ru ;;
ru-do|ru_do) cmd_ru_do ;;
status) cmd_status ;;
verify) cmd_verify ;;
-h|--help|help) usage ;;
"")
usage
exit 1
;;
*)
echo "Неизвестная команда: $1"
usage
exit 1
;;
esac