Improved Windows script

This commit is contained in:
DazedAnon 2026-05-09 16:15:37 -05:00
parent 8663c731e8
commit 738d572b6f
3 changed files with 227 additions and 73 deletions

View file

@ -5,6 +5,9 @@
## Future Patching
1. Run GAMEUPDATE.bat to auto patch.
2. Keep the whole `gameupdate` folder together when you ship the game—`patch.bat` and `patch-download.ps1` must sit side by side.
3. Optional: set `GAMEUPDATE_PROMPT_PWSH=1` before running `GameUpdate.bat` if you want users to be prompted to install PowerShell 7 via winget.
4. Optional: set `GAMEUPDATE_DL_ATTEMPTS` (default `2`) to control retries for API checks/downloads. Lower values fail faster; higher values tolerate flaky networks.
# Troubleshooting
**GAMEUPDATE.bat doesn't update and closes immediately**

View file

@ -0,0 +1,169 @@
#Requires -Version 5.1
param(
[Parameter(Mandatory = $true)]
[string]$GameRoot,
[Parameter(Mandatory = $true)]
[string]$StateFile
)
$ErrorActionPreference = 'Stop'
$exitCode = 1
$lockPath = Join-Path $GameRoot '.patch_update.lock'
if ($PSVersionTable.PSEdition -ne 'Core') {
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}
catch {}
}
function Get-EnvInt {
param(
[Parameter(Mandatory = $true)][string]$Name,
[Parameter(Mandatory = $true)][int]$Default
)
$raw = [Environment]::GetEnvironmentVariable($Name)
if ([string]::IsNullOrWhiteSpace($raw)) { return $Default }
$parsed = 0
if ([int]::TryParse([string]$raw, [ref]$parsed) -and $parsed -gt 0) { return $parsed }
return $Default
}
function Invoke-WithRetry {
param(
[Parameter(Mandatory = $true)][scriptblock]$Action,
[Parameter(Mandatory = $true)][string]$Name,
[int]$Attempts = 2,
[int]$DelaySeconds = 2
)
$lastErr = $null
for ($i = 1; $i -le $Attempts; $i++) {
try {
return & $Action
}
catch {
$lastErr = $_
if ($i -lt $Attempts) {
Write-Host ("{0} failed ({1}/{2}), retrying..." -f $Name, $i, $Attempts)
Start-Sleep -Seconds $DelaySeconds
}
}
}
throw $lastErr
}
function Download-WithIwrSpeedProgress {
param(
[Parameter(Mandatory = $true)][string]$Url,
[Parameter(Mandatory = $true)][string]$OutFile,
[Parameter(Mandatory = $true)][hashtable]$Headers
)
if (Test-Path -LiteralPath $OutFile) {
Remove-Item -LiteralPath $OutFile -Force
}
# Fast path: let Invoke-WebRequest handle transfer and progress internally.
# Custom stream/polling progress gives richer stats but adds measurable overhead.
$prevPref = $ProgressPreference
try {
$ProgressPreference = 'Continue'
Invoke-WebRequest -Uri $Url -OutFile $OutFile -Headers $Headers -UseBasicParsing
}
finally {
$ProgressPreference = $prevPref
}
}
try {
Set-Location -LiteralPath $GameRoot
if (Test-Path -LiteralPath $lockPath) {
throw 'PATCH_ERR:LOCK:Another patch process appears to be running.'
}
New-Item -Path $lockPath -ItemType File -Force | Out-Null
if (-not $env:GU_USERNAME -or -not $env:GU_REPO -or -not $env:GU_BRANCH) {
throw 'PATCH_ERR:CONFIG:patch.bat must set GU_USERNAME, GU_REPO, and GU_BRANCH.'
}
$id = [uri]::EscapeDataString($env:GU_USERNAME + '/' + $env:GU_REPO)
$branch = [uri]::EscapeDataString($env:GU_BRANCH)
$ua = 'DazedMTL-Patcher-1.0'
$headers = @{ 'User-Agent' = $ua }
$dlAttempts = Get-EnvInt -Name 'GAMEUPDATE_DL_ATTEMPTS' -Default 2
$latestSha = Invoke-WithRetry -Name 'Resolve latest patch SHA' -Attempts $dlAttempts -Action {
(Invoke-RestMethod -Uri ("https://gitgud.io/api/v4/projects/$id/repository/branches/$branch") -Headers $headers).commit.id
}
if (-not $latestSha) { throw 'PATCH_ERR:API:Latest commit SHA response was empty.' }
$latestSha = ([string]$latestSha).Trim()
$previousSha = ''
if (Test-Path -LiteralPath $StateFile) {
$previousSha = (Get-Content -LiteralPath $StateFile -Raw).Trim()
} else {
Write-Host 'Previous SHA hash not found. Assuming first time patching...'
}
if ($previousSha -eq $latestSha) {
$exitCode = 10
return
}
Write-Host 'Update found! Patching...'
Write-Host 'Downloading latest patch... (backend: iwr, GitLab API)'
$zipPath = Join-Path $PWD.Path 'repo.zip'
$stage = Join-Path $PWD.Path '_patch_extract_tmp'
$archiveSha = [uri]::EscapeDataString($latestSha)
$archiveUrl = "https://gitgud.io/api/v4/projects/$id/repository/archive.zip?sha=$archiveSha"
$dlSw = [System.Diagnostics.Stopwatch]::StartNew()
Invoke-WithRetry -Name 'Download archive with Invoke-WebRequest' -Attempts $dlAttempts -Action {
Download-WithIwrSpeedProgress -Url $archiveUrl -OutFile $zipPath -Headers $headers
} | Out-Null
$dlSw.Stop()
$bytes = (Get-Item -LiteralPath $zipPath).Length
$secs = [Math]::Max($dlSw.Elapsed.TotalSeconds, 0.001)
$mbps = ($bytes / 1MB) / $secs
Write-Host ("Download complete via iwr: {0:N1} MB in {1:N1}s ({2:N1} MB/s)" -f ($bytes / 1MB), $secs, $mbps)
$fs = [System.IO.File]::OpenRead($zipPath)
try {
$hdr = New-Object byte[] 4
if ($fs.Read($hdr, 0, 4) -lt 4 -or $hdr[0] -ne 0x50 -or $hdr[1] -ne 0x4B) {
throw 'PATCH_ERR:ZIP:Download is not a valid ZIP (error page or empty file).'
}
}
finally {
$fs.Dispose()
}
if (Test-Path -LiteralPath $stage) { Remove-Item -LiteralPath $stage -Recurse -Force }
New-Item -ItemType Directory -Path $stage | Out-Null
try {
Expand-Archive -LiteralPath $zipPath -DestinationPath $stage -Force
$dirs = @(Get-ChildItem -LiteralPath $stage -Directory)
if ($dirs.Count -ne 1) {
throw ('PATCH_ERR:ZIP:Expected one root folder in archive, found {0}.' -f $dirs.Count)
}
Copy-Item -Path (Join-Path $dirs[0].FullName '*') -Destination $PWD.Path -Recurse -Force
}
finally {
if (Test-Path -LiteralPath $stage) { Remove-Item -LiteralPath $stage -Recurse -Force }
}
Set-Content -LiteralPath $StateFile -Value $latestSha -Encoding Ascii
$exitCode = 0
}
catch {
Write-Host ''
Write-Host ($_.Exception.Message)
if ($_.InvocationInfo.PositionMessage) { Write-Host $_.InvocationInfo.PositionMessage }
$exitCode = 1
}
finally {
if (Test-Path -LiteralPath $lockPath) {
Remove-Item -LiteralPath $lockPath -Force -ErrorAction SilentlyContinue
}
}
exit $exitCode

View file

@ -29,41 +29,38 @@ echo Using root directory: "%ROOT_DIR%"
echo Using config: "%CONFIG_FILE%"
echo Checking for pwsh...
set _my_shell=pwsh
REM Check if pwsh.exe exists
set "_my_shell=pwsh"
where /q !_my_shell!
if !errorlevel! neq 0 (
echo pwsh not found.
echo.
echo PowerShell 7 ^(pwsh^) is faster and recommended.
set /p "INSTALL_PWSH=Would you like to install it now via winget? (Y/N): "
if /I "!INSTALL_PWSH!"=="Y" (
echo Installing PowerShell 7 via winget...
winget install --id Microsoft.PowerShell --source winget --accept-package-agreements --accept-source-agreements
if !errorlevel! neq 0 (
echo Install failed or winget not available. Falling back to powershell...
set _my_shell=powershell
if /I "%GAMEUPDATE_PROMPT_PWSH%"=="1" (
echo.
set /p "INSTALL_PWSH=PowerShell 7 is faster. Install now via winget? (Y/N): "
if /I "!INSTALL_PWSH!"=="Y" (
echo Installing PowerShell 7 via winget...
winget install --id Microsoft.PowerShell --source winget --accept-package-agreements --accept-source-agreements
where /q pwsh
if !errorlevel! equ 0 (
set "_my_shell=pwsh"
echo PowerShell 7 installed; using pwsh.
) else (
echo Install failed or unavailable; falling back to powershell.
set "_my_shell=powershell"
)
) else (
echo PowerShell 7 installed successfully.
echo Please re-run GameUpdate.bat to use pwsh.
pause
exit /b 0
echo Skipping install; falling back to powershell.
set "_my_shell=powershell"
)
) else (
echo Skipping install. Falling back to powershell...
set _my_shell=powershell
echo Tip: Set GAMEUPDATE_PROMPT_PWSH=1 to offer PowerShell 7 install.
echo Falling back to powershell...
set "_my_shell=powershell"
)
REM Check if powershell.exe exists
echo Checking for powershell...
where /q !_my_shell!
if !errorlevel! neq 0 (
echo.Error: Powershell not found!
echo ERROR: Neither pwsh nor powershell was found.
pause
exit /B 1
) else (
echo powershell found.
exit /b 1
)
) else (
echo pwsh found.
@ -85,11 +82,27 @@ for /f "usebackq tokens=1,2 delims==" %%a in ("%CONFIG_FILE%") do (
if "%%a"=="branch" set "branch=%%b"
)
REM Validate required config keys early for clearer errors
if "%username%"=="" (
echo ERROR: 'username=' is missing in gameupdate\patch-config.txt
pause
exit /b 1
)
if "%repo%"=="" (
echo ERROR: 'repo=' is missing in gameupdate\patch-config.txt
pause
exit /b 1
)
if "%branch%"=="" (
echo ERROR: 'branch=' is missing in gameupdate\patch-config.txt
pause
exit /b 1
)
REM For PowerShell (proper URL encoding; avoids cmd %% pitfalls)
set "GU_USERNAME=%username%"
set "GU_REPO=%repo%"
set "GU_BRANCH=%branch%"
if /I "!_my_shell!"=="pwsh" (set "GU_DL_PROGRESS=Continue") else (set "GU_DL_PROGRESS=SilentlyContinue")
REM --------------------------------------------------------
REM PRE-SETUP: Ensure SRPG data and patch structure exists
@ -150,56 +163,28 @@ if exist "%ROOT_DIR%\data.dts" (
echo [Pre-Setup] data.dts not found; skipping pre-setup SRPG steps.
)
REM Get the latest hash (GitLab API — browser /-/archive/ URLs are often blocked by CDN)
echo "Getting latest commit SHA hash"
!_my_shell! -NoProfile -Command "$ErrorActionPreference='Stop'; $id=[uri]::EscapeDataString($env:GU_USERNAME+'/'+ $env:GU_REPO); $br=[uri]::EscapeDataString($env:GU_BRANCH); (Invoke-RestMethod -Uri \"https://gitgud.io/api/v4/projects/$id/repository/branches/$br\" -Headers @{'User-Agent'='GameUpdate/1.0'}).commit.id" > "%ROOT_DIR%\latest_patch_sha.txt"
REM Read the latest SHA from the file
set /p latest_patch_sha=<"%ROOT_DIR%\latest_patch_sha.txt"
REM Check if previous_patch_sha.txt exists in gameupdate
if not exist "%SCRIPT_DIR%previous_patch_sha.txt" (
echo "Previous SHA hash not found!"
echo "Assuming first time patching..."
goto download_extract
)
REM Read the stored SHA from previous check
set /p previous_patch_sha=<"%SCRIPT_DIR%previous_patch_sha.txt"
REM Trim whitespace from SHA strings
set "previous_patch_sha=%previous_patch_sha: =%"
set "latest_patch_sha=%latest_patch_sha: =%"
REM Compare trimmed SHAs
if "%latest_patch_sha%" neq "%previous_patch_sha%" (
echo "Update found! Patching..."
goto download_extract
) else (
echo "Patch is up to date."
)
REM Delete latest_patch_sha.txt
del "%ROOT_DIR%\latest_patch_sha.txt"
endlocal
pause
exit /b
:download_extract
REM Escape single quotes in paths
set "escaped_root=%ROOT_DIR:'=''%"
REM Download zip via GitLab API; extract single top-level folder (API uses commit-based root name, not repo-branch)
echo "Downloading latest patch..."
!_my_shell! -NoProfile -Command "$ErrorActionPreference='Stop'; Set-Location -LiteralPath '%escaped_root%'; $id=[uri]::EscapeDataString($env:GU_USERNAME+'/'+ $env:GU_REPO); $shaQ=[uri]::EscapeDataString($env:GU_BRANCH); $url=\"https://gitgud.io/api/v4/projects/$id/repository/archive.zip?sha=$shaQ\"; $ProgressPreference=$env:GU_DL_PROGRESS; Invoke-WebRequest -Uri $url -OutFile 'repo.zip' -UseBasicParsing -Headers @{'User-Agent'='GameUpdate/1.0'}; $ProgressPreference='SilentlyContinue'; $stage = Join-Path (Get-Location) '_patch_extract_tmp'; if (Test-Path $stage) { Remove-Item -LiteralPath $stage -Recurse -Force }; New-Item -ItemType Directory -Path $stage | Out-Null; try { Expand-Archive -LiteralPath '.\repo.zip' -DestinationPath $stage -Force; $dirs = @(Get-ChildItem -LiteralPath $stage -Directory); if ($dirs.Count -ne 1) { throw \"Expected one root folder in archive, found $($dirs.Count)\" }; Copy-Item -Path (Join-Path $dirs[0].FullName '*') -Destination (Get-Location) -Recurse -Force } finally { if (Test-Path $stage) { Remove-Item -LiteralPath $stage -Recurse -Force } }"
if !errorlevel! neq 0 (
REM Download/extract via patch-download.ps1 (IWR-based); Bypass avoids ExecutionPolicy prompts
if not exist "%SCRIPT_DIR%patch-download.ps1" (
echo ERROR: patch-download.ps1 not found next to patch.bat in the gameupdate folder.
pause
exit /b 1
)
!_my_shell! -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%patch-download.ps1" -GameRoot "%ROOT_DIR%" -StateFile "%SCRIPT_DIR%previous_patch_sha.txt"
set "PATCH_DL_EXIT=!errorlevel!"
if "!PATCH_DL_EXIT!"=="10" (
echo Patch is up to date.
endlocal
pause
exit /b 0
)
if not "!PATCH_DL_EXIT!"=="0" (
echo Download or extraction failed!
if exist "%ROOT_DIR%\repo.zip" del "%ROOT_DIR%\repo.zip"
if exist "%ROOT_DIR%\_patch_extract_tmp" rmdir /s /q "%ROOT_DIR%\_patch_extract_tmp"
pause
exit /b
exit /b 1
)
echo "Applying patch..."
REM Files merged by Copy-Item above
@ -248,9 +233,6 @@ REM Clean up
echo "Cleaning up..."
del "%ROOT_DIR%\repo.zip"
if exist "%ROOT_DIR%\_patch_extract_tmp" rmdir /s /q "%ROOT_DIR%\_patch_extract_tmp"
del "%ROOT_DIR%\latest_patch_sha.txt"
REM Store latest SHA for next check in gameupdate
echo %latest_patch_sha% > "%SCRIPT_DIR%previous_patch_sha.txt"
endlocal
pause
exit /b