forked from FlyingEwok/MinecraftSplitscreenSteamdeck
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinstall-minecraft-splitscreen.sh
More file actions
executable file
·460 lines (410 loc) · 20.1 KB
/
Copy pathinstall-minecraft-splitscreen.sh
File metadata and controls
executable file
·460 lines (410 loc) · 20.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
#!/bin/bash
# =============================================================================
# MINECRAFT SPLITSCREEN INSTALLER — MODULAR ENTRY POINT
# =============================================================================
# Clean entry point: sources installer modules (from a local checkout or
# downloaded to a temp dir, cleaned up on exit) and runs the full install —
# Java + PolyMC setup, Fabric + mod install, instance creation, and optional
# Steam/desktop integration. No manual setup required; just run this script.
#
# Features:
# - Temporary module download + auto-cleanup when no local checkout exists
# - Automatic Java detection and installation
# - Fabric dependency chain (loader + mappings + LWJGL) resolution
# - API filtering for Fabric-compatible mods (Modrinth + CurseForge)
# - Steam Deck optimized installation; Steam + desktop integration
#
# This file DEFINES the installer globals world (see the GLOBAL VARIABLES
# block below). Globals PROVIDED: REPO_REF, MCSS_REPO_RAW_URL,
# MODRINTH_API_BASE, CURSEFORGE_API_BASE, FABRIC_META_BASE,
# MCSS_MAX_PLAYERS, MCSS_INSTANCE_PREFIX, MCSS_ACCOUNT_PREFIX,
# MCSS_MAX_MEM_MB, MCSS_MIN_MEM_MB, TARGET_DIR, ASSUME_YES, and the
# MODS/SUPPORTED_MODS/MOD_* arrays (populated by load_mods_config()).
#
# Note: installer modules are SOURCED (not executed standalone) and
# intentionally omit `set -euo pipefail` of their own — this entry script's
# strict mode governs the whole process. That is a deliberate code decision,
# not an oversight.
#
# Version history (one line per version; details live in git; max 6 lines):
# v1.7 2026-07-30 #91: lwjgl_management.sh -> version_management.sh;
# steam_integration+desktop_launcher -> system_integration;
# launcher_setup split -> +runtime_deploy.sh
# v1.6 2026-07-29 #89: version_stamp.sh added to INSTALLER_MODULE_FILES
# v1.5 2026-07-19 #89: read_runtime_manifest documented as the canonical
# reader (launcher_setup.sh now reuses it; #38 PR2)
# v1.4 2026-07-18 Account prefix "P" -> "Player" for MC account names
# v1.3 2026-07-17 Fix #87: canonical JVM heap-default home + paired guards
# v1.2 2026-07-10 Fix #45 PR3: API base constants, MCSS_REPO_RAW_URL home,
# runtime_modules.list — one manifest, four readers (#49)
# v1.1 2026-07-06 Fix: curl|bash bootstrap survives unset BASH_SOURCE
# =============================================================================
set -euo pipefail # Exit on error, undefined vars, pipe failures
# Runtime flags
DEBUG_MODE=false
# #185: was accepted and silently discarded — every prompt still blocked on
# stdin regardless. Now consumed by mcss_prompt (utilities.sh) at every
# installer prompt; see that function for what each one defaults to.
ASSUME_YES=false
# Parse installer flags early so startup/module logs can respect debug mode.
declare -a FORWARDED_ARGS=()
for arg in "$@"; do
case "$arg" in
--debug)
DEBUG_MODE=true
;;
--yes)
ASSUME_YES=true
;;
*)
FORWARDED_ARGS+=("$arg")
;;
esac
done
set -- "${FORWARDED_ARGS[@]}"
# =============================================================================
# CLEANUP AND SIGNAL HANDLING
# =============================================================================
# Global variable for modules directory (will be set later)
MODULES_DIR=""
# cleanup: Remove the temporary modules directory. Registered via `trap ...
# EXIT INT TERM` so it runs on normal exit or interruption alike.
# Inputs:
# Globals: MODULES_DIR (read)
# Outputs:
# side effects — rm -rf "$MODULES_DIR" if it was ever created
cleanup() {
if [[ -n "$MODULES_DIR" ]] && [[ -d "$MODULES_DIR" ]]; then
echo "🧹 Cleaning up temporary modules..."
rm -rf "$MODULES_DIR"
fi
}
# Set up trap to cleanup on script exit (normal or error)
trap cleanup EXIT INT TERM
# =============================================================================
# MODULE DOWNLOADING AND LOADING
# =============================================================================
# Get the directory where this script is located
# curl|bash delivers this script on STDIN: BASH_SOURCE is unset there, and the
# sourced modules leak set -u, so any bare reference is fatal (post-merge verify,
# 2026-07-06). $0 is "bash" in that mode → SCRIPT_DIR falls back to the CWD, and
# mods.conf lookup falls back to built-in defaults by design.
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
# Create a temporary directory for modules that will be cleaned up automatically
MODULES_DIR="$(mktemp -d -t minecraft-modules-XXXXXX)"
# Repo ref (branch/tag/commit) to install FROM. Defaults to 'main'; override to test a
# branch WITHOUT promoting it — e.g.:
# REPO_REF=feat/gamescope-windowing ./install-minecraft-splitscreen.sh
# Exported so every sourced module's download URL uses the same ref.
export REPO_REF="${REPO_REF:-main}"
# Single home for the repo's raw-content URL (D15/#45 PR 3): every file the
# installer chain fetches (modules, launcher, accounts.json, add-to-steam.py)
# builds its URL from this instead of retyping the host/repo/ref triple.
# Exported so sourced modules and child processes use the same ref.
export MCSS_REPO_RAW_URL="https://raw.githubusercontent.com/aradanmn/MinecraftSplitscreenSteamdeck/${REPO_REF}"
# Mod-platform API bases (#45 PR 3): one home per service instead of retyped
# hosts at every query site. mod_management.sh's sites migrate onto these in
# the BYOK branch (wip/curseforge-byok), which rewrites that code anyway.
export MODRINTH_API_BASE="${MODRINTH_API_BASE:-https://api.modrinth.com/v2}"
export CURSEFORGE_API_BASE="${CURSEFORGE_API_BASE:-https://api.curseforge.com/v1}"
export FABRIC_META_BASE="${FABRIC_META_BASE:-https://meta.fabricmc.net/v2}"
# GitHub repository information (modify these URLs to match your actual repository)
readonly REPO_BASE_URL="${MCSS_REPO_RAW_URL}/modules"
# Installer modules — sourced during installation to run the setup workflow.
readonly INSTALLER_MODULE_FILES=(
"utilities.sh"
"version_stamp.sh"
"java_management.sh"
"evsieve_management.sh"
"launcher_setup.sh"
"runtime_deploy.sh"
"version_management.sh"
"mod_management.sh"
"instance_creation.sh"
"system_integration.sh"
"main_workflow.sh"
)
# Runtime orchestrator modules — deployed to TARGET_DIR/modules/ so the launcher
# can source them at play time. NOT sourced by the installer. Their list lives
# in modules/runtime_modules.list (#49: ONE manifest — also read by the
# launcher, launcher_setup.sh and deploy.sh); RUNTIME_MODULE_FILES and
# MODULE_FILES are populated from it below, before the download and
# presence-check steps that consume them.
readonly RUNTIME_MANIFEST_NAME="runtime_modules.list"
declare -a RUNTIME_MODULE_FILES=()
# read_runtime_manifest: Print manifest entries, ignoring comments/blanks.
# #89: the CANONICAL definition of the manifest parser — three other sites
# duplicate this exact rule (modules/launcher_setup.sh reuses this very
# function object via a same-process soft guard when sourced from here, the
# normal path; minecraftSplitscreen.sh and deploy.sh each carry their own
# copy with a comment pointing back here, because neither runs in the same
# process as this installer entry — see their copies for why). Any change
# to the parse rule must be mirrored in all three.
# Inputs:
# $1 — path to a runtime_modules.list-format manifest
# Outputs:
# stdout — one module filename per line
read_runtime_manifest() {
grep -vE '^[[:space:]]*(#|$)' "$1" 2>/dev/null
}
# download_modules: Download every module in MODULE_FILES to MODULES_DIR.
# Inputs:
# Globals: MODULE_FILES, MODULES_DIR, REPO_BASE_URL, DEBUG_MODE (read)
# Outputs:
# side effects — module files written + chmod +x under MODULES_DIR
# exit 1 — if any module fails to download, or neither curl nor wget exist
download_modules() {
echo "🔄 Downloading required modules to temporary directory..."
if [[ "$DEBUG_MODE" == true ]]; then
echo "📁 Temporary modules directory: $MODULES_DIR"
echo "🌐 Repository URL: $REPO_BASE_URL"
fi
# Temporarily disable strict error handling for downloads
set +e
# The temporary directory is already created by mktemp
local downloaded_count=0
local failed_count=0
# Download each required module
for module in "${MODULE_FILES[@]}"; do
local module_path="$MODULES_DIR/$module"
local module_url="$REPO_BASE_URL/$module"
if [[ "$DEBUG_MODE" == true ]]; then
echo "⬇️ Downloading module: $module"
echo " URL: $module_url"
fi
# Download the module file
if command -v curl >/dev/null 2>&1; then
curl_output=$(curl -fsSL "$module_url" -o "$module_path" 2>&1)
curl_exit_code=$?
if [[ $curl_exit_code -eq 0 ]]; then
chmod +x "$module_path"
((downloaded_count++))
if [[ "$DEBUG_MODE" == true ]]; then
echo "✅ Downloaded: $module"
fi
else
echo "❌ Failed to download: $module"
echo " Curl exit code: $curl_exit_code"
echo " Error: $curl_output"
((failed_count++))
fi
elif command -v wget >/dev/null 2>&1; then
wget_output=$(wget -q "$module_url" -O "$module_path" 2>&1)
wget_exit_code=$?
if [[ $wget_exit_code -eq 0 ]]; then
chmod +x "$module_path"
((downloaded_count++))
if [[ "$DEBUG_MODE" == true ]]; then
echo "✅ Downloaded: $module"
fi
else
echo "❌ Failed to download: $module"
echo " Wget exit code: $wget_exit_code"
echo " Error: $wget_output"
((failed_count++))
fi
else
echo "❌ Error: Neither curl nor wget is available"
echo "Please install curl or wget to download modules automatically"
echo "Or manually download all modules from: $REPO_BASE_URL"
# Re-enable strict error handling before exiting
set -euo pipefail
exit 1
fi
done
# Re-enable strict error handling
set -euo pipefail
if [[ $failed_count -gt 0 ]]; then
echo "❌ Failed to download $failed_count module(s)"
echo "ℹ️ This might be because:"
echo " - The repository doesn't exist or is private"
echo " - The modules haven't been uploaded to the repository yet"
echo " - Network connectivity issues"
echo ""
echo "🔧 For now, you can place the modules manually in the same directory as this script:"
echo " mkdir -p '$SCRIPT_DIR/modules'"
echo " # Then copy all .sh module files to that directory"
echo ""
echo "🌐 Or check if the repository exists at: https://github.com/aradanmn/MinecraftSplitscreenSteamdeck"
exit 1
fi
echo "✅ Downloaded $downloaded_count module(s) to temporary directory"
echo "ℹ️ Modules will be automatically cleaned up when script completes"
}
# Acquire the runtime-module MANIFEST first — the download and presence steps
# below derive from it. A local checkout's cp brings it along; a curl|bash
# install fetches just the manifest, then downloads everything it names.
if [[ -d "$SCRIPT_DIR/modules" ]]; then
if [[ "$DEBUG_MODE" == true ]]; then
echo "📁 Found local modules directory, copying to temporary location..."
fi
cp -r "$SCRIPT_DIR/modules/"* "$MODULES_DIR/"
chmod +x "$MODULES_DIR"/*.sh
if [[ "$DEBUG_MODE" == true ]]; then
echo "✅ Copied local modules to temporary directory"
fi
else
_manifest_url="$REPO_BASE_URL/$RUNTIME_MANIFEST_NAME"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$_manifest_url" -o "$MODULES_DIR/$RUNTIME_MANIFEST_NAME" 2>/dev/null || true
elif command -v wget >/dev/null 2>&1; then
wget -q "$_manifest_url" -O "$MODULES_DIR/$RUNTIME_MANIFEST_NAME" 2>/dev/null || true
fi
fi
mapfile -t RUNTIME_MODULE_FILES < <(read_runtime_manifest "$MODULES_DIR/$RUNTIME_MANIFEST_NAME")
if [[ ${#RUNTIME_MODULE_FILES[@]} -eq 0 ]]; then
echo "❌ Error: could not load the runtime module manifest ($RUNTIME_MANIFEST_NAME)"
echo " Expected in the local modules/ dir or at: $REPO_BASE_URL/$RUNTIME_MANIFEST_NAME"
echo " Refusing to continue: an empty manifest would install a launcher with no runtime modules (#49)."
exit 1
fi
readonly RUNTIME_MODULE_FILES
# Combined list used by download_modules and the presence check below.
readonly MODULE_FILES=("${INSTALLER_MODULE_FILES[@]}" "${RUNTIME_MODULE_FILES[@]}")
# In download mode the modules themselves are still missing — fetch them now
# that the manifest says what to fetch.
if [[ ! -d "$SCRIPT_DIR/modules" ]]; then
download_modules
fi
# Verify all modules are now present
for module in "${MODULE_FILES[@]}"; do
if [[ ! -f "$MODULES_DIR/$module" ]]; then
echo "❌ Error: Required module missing: $module"
echo "Please check your internet connection or download manually from:"
echo "$REPO_BASE_URL/$module"
exit 1
fi
done
# Source installer modules to load their functions (dependency order).
# Runtime orchestrator modules (dock_detection, controller_monitor, etc.) are
# deployed to TARGET_DIR/modules/ by install_runtime_modules() — not sourced here.
source "$MODULES_DIR/utilities.sh"
# #89: launcher_setup.sh's stamping calls into this — must load before it.
source "$MODULES_DIR/version_stamp.sh"
# preflight.sh is a runtime module, but we source it at INSTALL time too so the dependency
# hard-stop (_preflight_deps install) actually runs before we download/install anything
# (G1: it was previously never sourced, so the install-time check silently no-op'd).
source "$MODULES_DIR/preflight.sh"
source "$MODULES_DIR/java_management.sh"
source "$MODULES_DIR/evsieve_management.sh"
source "$MODULES_DIR/launcher_setup.sh"
source "$MODULES_DIR/runtime_deploy.sh"
source "$MODULES_DIR/version_management.sh"
source "$MODULES_DIR/mod_management.sh"
source "$MODULES_DIR/instance_creation.sh"
source "$MODULES_DIR/system_integration.sh"
source "$MODULES_DIR/main_workflow.sh"
# =============================================================================
# GLOBAL VARIABLES
# =============================================================================
# Script configuration paths
readonly TARGET_DIR="$HOME/.local/share/PolyMC"
# --- Installer-side constants (PAIRED with modules/runtime_context.sh) -------
# The installer runs as a SEPARATE PROCESS from the launcher (often via
# curl|bash with no checkout), so it cannot source runtime_context.sh. These
# are the INSTALL-TIME home of constants whose PLAY-TIME home is
# runtime_context.sh — when changing one, grep the same MCSS_ name there and
# change both (#45 PR 3 / PLAN Part 4 "two homes, documented pairing").
readonly MCSS_MAX_PLAYERS=4 # pairs runtime_context.sh:MCSS_MAX_PLAYERS
readonly MCSS_INSTANCE_PREFIX="latestUpdate-" # pairs runtime_context.sh:MCSS_INSTANCE_PREFIX
readonly MCSS_ACCOUNT_PREFIX="Player" # pairs runtime_context.sh:MCSS_ACCOUNT_PREFIX
export MCSS_MAX_PLAYERS MCSS_INSTANCE_PREFIX MCSS_ACCOUNT_PREFIX
# Fix #87: JVM heap defaults — canonical home. Previously defaulted ONLY in
# instance_creation.sh and consumed by launcher_setup.sh's
# configure_polymc_defaults() with no fallback of its own (source-order
# coupling: a launcher_setup.sh sourced/called without instance_creation.sh
# having run first silently wrote an empty MaxMemAlloc/MinMemAlloc). Both
# modules/instance_creation.sh and modules/launcher_setup.sh keep their own
# `: "${...:=...}"` guard too (PAIRED WITH this block) —
# tests/test_installer.sh sources launcher_setup.sh standalone, so a single
# define-once site can't cover every sourcing path. Same values everywhere.
: "${MCSS_MAX_MEM_MB:=3072}"
: "${MCSS_MIN_MEM_MB:=512}"
# Runtime variables (set during execution)
JAVA_PATH=""
MC_VERSION=""
FABRIC_VERSION=""
LWJGL_VERSION=""
# Mod configuration arrays — populated by load_mods_config() below.
declare -a REQUIRED_SPLITSCREEN_MODS=()
declare -a REQUIRED_SPLITSCREEN_IDS=()
declare -a REQUIRED_SPLITSCREEN_PLATFORMS=()
declare -a MODS=()
# Dependency map: mod name → comma-separated names of mods it requires.
# Used by resolve_conf_dependencies() in mod_management.sh.
declare -A MOD_DEPS_BY_NAME=()
# load_mods_config: Populate MODS, REQUIRED_SPLITSCREEN_MODS, and
# REQUIRED_SPLITSCREEN_IDS from mods.conf (next to this script).
# Falls back to built-in defaults if the file is missing.
load_mods_config() {
local conf="${SCRIPT_DIR}/mods.conf"
if [[ ! -f "$conf" ]]; then
echo "[mods] mods.conf not found at ${conf} — using built-in defaults" >&2
# NOTE: the "Splitscreen Support" mod (yJgqfSDR) is NO LONGER installed — window
# tiling is done by KWin, not the mod (2026-06-23).
# Standard performance set as of 2026-07-17 — see mods.conf for rationale.
REQUIRED_SPLITSCREEN_MODS=("Controlify" "Sodium" "Lithium" "FerriteCore" "ModernFix" "Entity Culling" "ImmediatelyFast")
REQUIRED_SPLITSCREEN_IDS=("DOUdJVEm" "AANobbMI" "gvQqBUqZ" "uXXizFIs" "TjSm1wrD" "NNAgCjsB" "5ZwdcRci")
REQUIRED_SPLITSCREEN_PLATFORMS=("modrinth" "modrinth" "modrinth" "modrinth" "modrinth" "modrinth" "modrinth")
MODS=(
"Controlify|modrinth|DOUdJVEm"
"Sodium|modrinth|AANobbMI"
"Lithium|modrinth|gvQqBUqZ"
"FerriteCore|modrinth|uXXizFIs"
"ModernFix|modrinth|TjSm1wrD"
"Entity Culling|modrinth|NNAgCjsB"
"ImmediatelyFast|modrinth|5ZwdcRci"
)
MOD_DEPS_BY_NAME=()
return 0
fi
echo "[mods] Loading mod list from ${conf}" >&2
while IFS= read -r line || [[ -n "$line" ]]; do
# Strip inline comments, then leading/trailing whitespace
line="${line%%#*}"
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[[ -z "$line" ]] && continue
local type name platform id deps
IFS='|' read -r type name platform id deps <<< "$line"
# Trim whitespace from each field
type="${type// /}"
name="${name#"${name%%[![:space:]]*}"}"; name="${name%"${name##*[![:space:]]}"}"
platform="${platform// /}"; id="${id// /}"
deps="${deps#"${deps%%[![:space:]]*}"}"; deps="${deps%"${deps##*[![:space:]]}"}"
MODS+=("${name}|${platform}|${id}")
if [[ "$type" == "required" ]]; then
REQUIRED_SPLITSCREEN_MODS+=("$name")
REQUIRED_SPLITSCREEN_IDS+=("$id")
REQUIRED_SPLITSCREEN_PLATFORMS+=("$platform")
fi
if [[ -n "$deps" ]]; then
MOD_DEPS_BY_NAME["$name"]="$deps"
fi
done < "$conf"
echo "[mods] Loaded ${#MODS[@]} mods (${#REQUIRED_SPLITSCREEN_MODS[@]} required, ${#MOD_DEPS_BY_NAME[@]} with declared deps)" >&2
}
load_mods_config
# Runtime mod tracking arrays (populated during execution)
declare -a SUPPORTED_MODS=()
declare -a MOD_DESCRIPTIONS=()
declare -a MOD_URLS=()
declare -a MOD_IDS=()
declare -a MOD_TYPES=()
declare -a MOD_DEPENDENCIES=()
declare -a FINAL_MOD_INDEXES=()
declare -a MISSING_MODS=()
# =============================================================================
# SCRIPT ENTRY POINT
# =============================================================================
# Execute main function if script is run directly
# This allows the script to be sourced for testing without auto-execution
# ${BASH_SOURCE[0]:-$0}: under curl|bash BASH_SOURCE is unset (set -u fatal — the
# 'line 350 unbound variable' failure) and the fallback compares $0 to itself,
# so piped execution correctly runs main.
if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]] && [[ -z "${TESTING_MODE:-}" ]]; then
main "$@"
fi
# =============================================================================
# END OF MODULAR MINECRAFT SPLITSCREEN INSTALLER
# =============================================================================