238aa7809c
Lists local fixed drives with free space and lets the player pick one by number or letter. Enter keeps the previous location on the system drive (%USERPROFILE%\mc-lan-party), so existing installs are still found; other drives install to <drive>:\mc-lan-party. Warns under 3 GB free. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
134 lines
7.2 KiB
PowerShell
134 lines
7.2 KiB
PowerShell
# LAN party bootstrap for Windows: installs Fjord Launcher Unlocked (DRM-free
|
|
# Prism fork), seeds a persistent offline account, drops in the pre-built
|
|
# modpack instance, and launches straight into it on the LAN server.
|
|
#
|
|
# Hosted from Gitea (git.mivanchenko.de), not a local http.server — see HOSTING.md.
|
|
# Participants run: irm https://git.mivanchenko.de/mivanchenko/mc-lan-party/raw/branch/main/install.ps1 | iex
|
|
|
|
# ---- EDIT ME before hosting ----
|
|
$HostUrl = "https://git.mivanchenko.de/mivanchenko/mc-lan-party/raw/branch/main" # for this script + install.sh (small text files)
|
|
$ModpackUrl = "https://minecraft.mivanchenko.de/modpack.zip" # served directly by Caddy (static/ mount), NOT Gitea — modpack.zip is well over Gitea's 50MB upload cap
|
|
$InstanceName = "LANParty"
|
|
$ServerAddr = "minecraft.mivanchenko.de:25565" # always-on homelab box, same address for prep and the actual party
|
|
# ---------------------------------
|
|
|
|
$ErrorActionPreference = "Stop" # fail loudly instead of printing an error and silently continuing
|
|
|
|
# ---- pick the install drive (default: the system drive, under the user profile like before) ----
|
|
$SystemDrive = $env:SystemDrive # e.g. "C:"
|
|
$Drives = @(Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | Sort-Object DeviceID) # local fixed disks only
|
|
Write-Host "Where should the LAN party Minecraft be installed? (needs ~3 GB)"
|
|
for ($i = 0; $i -lt $Drives.Count; $i++) {
|
|
$d = $Drives[$i]
|
|
$free = [math]::Round($d.FreeSpace / 1GB, 1)
|
|
$label = if ($d.VolumeName) { " ($($d.VolumeName))" } else { "" }
|
|
$default = if ($d.DeviceID -eq $SystemDrive) { " [default]" } else { "" }
|
|
Write-Host (" {0}) {1}{2} - {3} GB free{4}" -f ($i + 1), $d.DeviceID, $label, $free, $default)
|
|
}
|
|
$DriveChoice = $null
|
|
while (-not $DriveChoice) {
|
|
$answer = Read-Host "Drive number or letter [$SystemDrive]"
|
|
if ([string]::IsNullOrWhiteSpace($answer)) { $answer = $SystemDrive }
|
|
$answer = $answer.Trim()
|
|
if ($answer -match '^\d+$' -and [int]$answer -ge 1 -and [int]$answer -le $Drives.Count) {
|
|
$DriveChoice = $Drives[[int]$answer - 1]
|
|
} else {
|
|
$letter = $answer.TrimEnd(':', '\').ToUpper() + ":"
|
|
$DriveChoice = $Drives | Where-Object { $_.DeviceID -eq $letter } | Select-Object -First 1
|
|
}
|
|
if (-not $DriveChoice) { Write-Host " '$answer' is not one of the drives above, try again." -ForegroundColor Yellow }
|
|
}
|
|
if ($DriveChoice.FreeSpace -lt 3GB) {
|
|
Write-Host "==> Warning: only $([math]::Round($DriveChoice.FreeSpace / 1GB, 1)) GB free on $($DriveChoice.DeviceID), the install may run out of space." -ForegroundColor Yellow
|
|
}
|
|
|
|
# System drive keeps the old location so existing installs are found again; other drives get a top-level folder
|
|
if ($DriveChoice.DeviceID -eq $SystemDrive) {
|
|
$InstallDir = Join-Path $env:USERPROFILE "mc-lan-party"
|
|
} else {
|
|
$InstallDir = Join-Path "$($DriveChoice.DeviceID)\" "mc-lan-party"
|
|
}
|
|
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
|
Set-Location $InstallDir
|
|
|
|
Write-Host "==> Setting up in $InstallDir"
|
|
|
|
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/hero-persson/FjordLauncherUnlocked/releases/latest"
|
|
$asset = $release.assets | Where-Object { $_.name -match "Windows-MSVC-Portable-.*\.zip$" } | Select-Object -First 1
|
|
if (-not $asset) {
|
|
$asset = $release.assets | Where-Object { $_.name -match "Windows-MinGW-w64-Portable-.*\.zip$" } | Select-Object -First 1
|
|
}
|
|
if (-not $asset) { throw "Could not find a Windows portable build in the latest Fjord Launcher Unlocked release." }
|
|
|
|
Write-Host "==> Downloading $($asset.name)"
|
|
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile "FjordLauncher.zip"
|
|
Expand-Archive -Path "FjordLauncher.zip" -DestinationPath "app" -Force
|
|
$LauncherBin = Get-ChildItem -Path "app" -Filter "fjordlauncher.exe" -Recurse | Select-Object -First 1 -ExpandProperty FullName
|
|
if (-not $LauncherBin) { throw "fjordlauncher.exe not found after extracting the portable build." }
|
|
|
|
# ---- offline account: compute Mojang-style offline UUID so playerdata persists across launches ----
|
|
$Nick = Read-Host "Minecraft nickname [$env:USERNAME]"
|
|
if ([string]::IsNullOrWhiteSpace($Nick)) { $Nick = $env:USERNAME }
|
|
|
|
function Get-OfflineUuid([string]$name) {
|
|
$md5 = [System.Security.Cryptography.MD5]::Create()
|
|
$bytes = $md5.ComputeHash([System.Text.Encoding]::UTF8.GetBytes("OfflinePlayer:$name"))
|
|
$bytes[6] = ($bytes[6] -band 0x0f) -bor 0x30
|
|
$bytes[8] = ($bytes[8] -band 0x3f) -bor 0x80
|
|
$hex = -join ($bytes | ForEach-Object { $_.ToString("x2") })
|
|
return "{0}-{1}-{2}-{3}-{4}" -f $hex.Substring(0,8), $hex.Substring(8,4), $hex.Substring(12,4), $hex.Substring(16,4), $hex.Substring(20,12)
|
|
}
|
|
|
|
$Uuid = Get-OfflineUuid $Nick
|
|
$md5 = [System.Security.Cryptography.MD5]::Create()
|
|
$clientTokenBytes = $md5.ComputeHash([System.Text.Encoding]::UTF8.GetBytes("ClientToken:$Nick"))
|
|
$ClientToken = -join ($clientTokenBytes | ForEach-Object { $_.ToString("x2") })
|
|
$Iat = [int][double]::Parse((Get-Date -UFormat %s))
|
|
|
|
$accounts = @{
|
|
accounts = @(
|
|
@{
|
|
active = $true
|
|
type = "Offline"
|
|
profile = @{ id = $Uuid; name = $Nick; capes = @(); skin = @{ id = ""; url = ""; variant = "" } }
|
|
entitlement = @{ canPlayMinecraft = $true; ownsMinecraft = $true }
|
|
ygg = @{ token = "0"; extra = @{ clientToken = $ClientToken; userName = $Nick }; iat = $Iat }
|
|
}
|
|
)
|
|
formatVersion = 3
|
|
}
|
|
$accounts | ConvertTo-Json -Depth 6 | Set-Content -Path "accounts.json" -Encoding UTF8
|
|
Write-Host "==> Offline account '$Nick' ready (UUID $Uuid)"
|
|
|
|
# ---- modpack instance ----
|
|
New-Item -ItemType Directory -Force -Path "instances" | Out-Null
|
|
if (-not (Test-Path (Join-Path "instances" $InstanceName))) {
|
|
Write-Host "==> Downloading modpack from $ModpackUrl"
|
|
try {
|
|
Invoke-WebRequest -Uri $ModpackUrl -OutFile "modpack.zip"
|
|
Expand-Archive -Path "modpack.zip" -DestinationPath "instances" -Force
|
|
} catch {
|
|
Write-Host "==> FAILED to fetch/extract the modpack from $ModpackUrl" -ForegroundColor Red
|
|
Write-Host " $($_.Exception.Message)" -ForegroundColor Red
|
|
Write-Host " (Fjord Launcher itself installed fine at $InstallDir\app — this just means modpack.zip isn't being served yet.)"
|
|
exit 1
|
|
}
|
|
Write-Host "==> Modpack installed to instances\$InstanceName"
|
|
} else {
|
|
Write-Host "==> Instance already present, skipping download"
|
|
}
|
|
|
|
# Desktop shortcut so the game is findable after closing it, without re-running this script
|
|
$ShortcutPath = Join-Path ([Environment]::GetFolderPath("Desktop")) "LAN Party Minecraft.lnk"
|
|
$WshShell = New-Object -ComObject WScript.Shell
|
|
$Shortcut = $WshShell.CreateShortcut($ShortcutPath)
|
|
$Shortcut.TargetPath = $LauncherBin
|
|
$Shortcut.Arguments = "--dir `"$InstallDir`" --launch $InstanceName --profile `"$Nick`" --server $ServerAddr"
|
|
$Shortcut.WorkingDirectory = Split-Path $LauncherBin
|
|
$Shortcut.IconLocation = $LauncherBin
|
|
$Shortcut.Save()
|
|
Write-Host "==> Desktop shortcut created: $ShortcutPath (double-click this next time instead of re-running the install script)"
|
|
|
|
Write-Host "==> Launching into $InstanceName as $Nick, joining $ServerAddr"
|
|
& $LauncherBin --dir $InstallDir --launch $InstanceName --profile $Nick --server $ServerAddr
|