Have one ps1 file because the mob said so
This commit is contained in:
parent
e8b541e51a
commit
64fe688c18
7 changed files with 531 additions and 414 deletions
|
|
@ -1,9 +1,69 @@
|
|||
@echo off
|
||||
setlocal
|
||||
setlocal EnableExtensions EnableDelayedExpansion
|
||||
|
||||
REM Copy patch.bat to a temp name so the live patch.bat can be overwritten during updates
|
||||
copy /Y "gameupdate\patch.bat" "gameupdate\patch2.bat" >nul
|
||||
call "gameupdate\patch2.bat"
|
||||
del /Q "gameupdate\patch2.bat" >nul 2>&1
|
||||
REM When deployed on a game: this file sits in the game root; patch scripts live in .\gameupdate\
|
||||
REM In this repo the same layout is kept for neatness: this bat is under DazedMTLTool\gameupdate\ next to a nested gameupdate\ folder with patch.ps1.
|
||||
set "GU_ROOT=%~dp0"
|
||||
REM Game root is this batch file's folder (not %%CD%%, so full-path launches still work).
|
||||
set "GAME_ROOT=!GU_ROOT!"
|
||||
if "!GAME_ROOT:~-1!"=="\" set "GAME_ROOT=!GAME_ROOT:~0,-1!"
|
||||
set "PATCH_SCRIPT_DIR=!GU_ROOT!gameupdate"
|
||||
|
||||
endlocal
|
||||
if not exist "!PATCH_SCRIPT_DIR!\patch.ps1" (
|
||||
echo ERROR: patch.ps1 not found at:
|
||||
echo !PATCH_SCRIPT_DIR!\patch.ps1
|
||||
echo Expected layout: GameUpdate.bat and a gameupdate folder next to each other ^(same parent^).
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Copy patch.ps1 so the live script can be overwritten during updates
|
||||
copy /Y "!PATCH_SCRIPT_DIR!\patch.ps1" "!PATCH_SCRIPT_DIR!\patch2.ps1" >nul
|
||||
if errorlevel 1 (
|
||||
echo ERROR: Could not copy patch.ps1 to patch2.ps1 in:
|
||||
echo !PATCH_SCRIPT_DIR!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set "_my_shell=pwsh"
|
||||
where /q !_my_shell!
|
||||
if !errorlevel! neq 0 (
|
||||
echo PowerShell 7 ^(pwsh^) not found.
|
||||
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 Skipping install; falling back to powershell.
|
||||
set "_my_shell=powershell"
|
||||
)
|
||||
) else (
|
||||
echo Tip: Set GAMEUPDATE_PROMPT_PWSH=1 to offer PowerShell 7 install.
|
||||
echo Falling back to powershell...
|
||||
set "_my_shell=powershell"
|
||||
)
|
||||
where /q !_my_shell!
|
||||
if !errorlevel! neq 0 (
|
||||
echo ERROR: Neither pwsh nor powershell was found.
|
||||
del /Q "!PATCH_SCRIPT_DIR!\patch2.ps1" >nul 2>&1
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
!_my_shell! -NoProfile -ExecutionPolicy Bypass -File "!PATCH_SCRIPT_DIR!\patch2.ps1" -GameRoot "!GAME_ROOT!"
|
||||
set "GU_PATCH_EXIT=!errorlevel!"
|
||||
del /Q "!PATCH_SCRIPT_DIR!\patch2.ps1" >nul 2>&1
|
||||
|
||||
exit /b !GU_PATCH_EXIT!
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@
|
|||
# Enable error handling
|
||||
set -e
|
||||
|
||||
# Copy patch.sh to a new file in gameupdate
|
||||
cp "gameupdate/patch.sh" "gameupdate/patch2.sh"
|
||||
# Same layout as GameUpdate.bat: patch assets live under ./gameupdate/ next to this file.
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
GAME_ROOT="$SCRIPT_DIR"
|
||||
PATCH_DIR="$SCRIPT_DIR/gameupdate"
|
||||
|
||||
# Run the new file
|
||||
bash "gameupdate/patch2.sh"
|
||||
|
||||
# Delete the new file
|
||||
rm "gameupdate/patch2.sh"
|
||||
cd "$GAME_ROOT"
|
||||
cp "$PATCH_DIR/patch.sh" "$PATCH_DIR/patch2.sh"
|
||||
bash "$PATCH_DIR/patch2.sh"
|
||||
rm "$PATCH_DIR/patch2.sh"
|
||||
|
|
|
|||
|
|
@ -4,10 +4,18 @@
|
|||
3. Extract to game folder and Replace All.
|
||||
|
||||
## 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.
|
||||
1. Run **`GameUpdate.bat`** to auto patch (Windows).
|
||||
|
||||
### Folder layout
|
||||
|
||||
**In this translation-tool repo:** Patch payloads stay tidy inside **`gameupdate/gameupdate/`** (`patch.ps1`, `patch-config.txt`, etc.). **`GameUpdate.bat`** and **`GameUpdate_linux.sh`** sit one level up, under **`gameupdate/`**.
|
||||
|
||||
**On an installed game (what gets copied over):** Put **`GameUpdate.bat`** in the **game root**, next to the game exe. Put **`patch.ps1`** and friends inside **`gameupdate\`** under that same root (mirror names—still **`gameupdate\`**). **`GameUpdate.bat`** finds **`gameupdate\patch.ps1`** from its own folder, so **`GameRoot`** is correct even if the console cwd is somewhere else.
|
||||
|
||||
**Copy checklist:** From **`DazedMTLTool/gameupdate/`**, copy **`GameUpdate.bat`** to `<game>\`; copy everything inside **`gameupdate/gameupdate/`** into `<game>\gameupdate\`.
|
||||
|
||||
2. Optional: set `GAMEUPDATE_PROMPT_PWSH=1` before running `GameUpdate.bat` if you want users to be prompted to install PowerShell 7 via winget.
|
||||
3. 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**
|
||||
|
|
|
|||
|
|
@ -1,159 +0,0 @@
|
|||
#Requires -Version 5.1
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$GameRoot,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$StateFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$exitCode = 1
|
||||
|
||||
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 (-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 'First run: comparing with remote...'
|
||||
}
|
||||
if ($previousSha -eq $latestSha) {
|
||||
exit 10
|
||||
}
|
||||
|
||||
Write-Host 'Downloading patch...'
|
||||
$zipPath = Join-Path $PWD.Path 'repo.zip'
|
||||
# Use a short temp extract root to avoid Windows MAX_PATH issues when
|
||||
# GitLab archive root folder includes long commit SHA suffixes.
|
||||
$stage = Join-Path ([IO.Path]::GetTempPath()) ("gu_" + [Guid]::NewGuid().ToString("N"))
|
||||
$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
|
||||
}
|
||||
|
||||
exit $exitCode
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
@echo off
|
||||
setlocal EnableExtensions
|
||||
|
||||
REM Check if being run from gameupdate folder (incorrect usage)
|
||||
for %%I in ("%CD%") do set "CURRENT_FOLDER=%%~nxI"
|
||||
if /I "%CURRENT_FOLDER%"=="gameupdate" (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo ERROR: Do not run patch.bat directly!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo You are running this from the gameupdate folder.
|
||||
echo This will not work correctly!
|
||||
echo.
|
||||
echo Please go back to the game's root folder and
|
||||
echo run GameUpdate.bat instead.
|
||||
echo ========================================
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Determine important paths
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "ROOT_DIR=%CD%"
|
||||
set "CONFIG_FILE=%SCRIPT_DIR%patch-config.txt"
|
||||
|
||||
echo Root: %ROOT_DIR%
|
||||
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
set "_my_shell=pwsh"
|
||||
where /q !_my_shell!
|
||||
if !errorlevel! neq 0 (
|
||||
echo PowerShell 7 ^(pwsh^) not found.
|
||||
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 Skipping install; falling back to powershell.
|
||||
set "_my_shell=powershell"
|
||||
)
|
||||
) else (
|
||||
echo Tip: Set GAMEUPDATE_PROMPT_PWSH=1 to offer PowerShell 7 install.
|
||||
echo Falling back to powershell...
|
||||
set "_my_shell=powershell"
|
||||
)
|
||||
where /q !_my_shell!
|
||||
if !errorlevel! neq 0 (
|
||||
echo ERROR: Neither pwsh nor powershell was found.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
REM Check if patch-config.txt exists in gameupdate folder
|
||||
if not exist "%CONFIG_FILE%" (
|
||||
echo Config file gameupdate\patch-config.txt not found - skipping patch.
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM Read configuration from file
|
||||
for /f "usebackq tokens=1,2 delims==" %%a in ("%CONFIG_FILE%") do (
|
||||
if "%%a"=="username" set "username=%%b"
|
||||
if "%%a"=="repo" set "repo=%%b"
|
||||
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%"
|
||||
|
||||
REM --------------------------------------------------------
|
||||
REM PRE-SETUP: Ensure SRPG data and patch structure exists
|
||||
REM Run Steps 1 and 2 BEFORE pulling repo patch to avoid overwriting updates
|
||||
REM 1) Unpack once if data folder doesn't exist (and data.dts does)
|
||||
REM 2) Create Patch once if patch folder doesn't exist
|
||||
REM --------------------------------------------------------
|
||||
set "UNPACKER=%ROOT_DIR%\SRPG_Unpacker.exe"
|
||||
if exist "%ROOT_DIR%\data.dts" (
|
||||
if exist "%UNPACKER%" (
|
||||
REM Step 1: Unpack (once)
|
||||
if not exist "%ROOT_DIR%\data\" (
|
||||
set "SHOULD_UNPACK=1"
|
||||
) else if not exist "%ROOT_DIR%\data\project.dat" (
|
||||
set "SHOULD_UNPACK=1"
|
||||
) else (
|
||||
set "SHOULD_UNPACK=0"
|
||||
)
|
||||
|
||||
if "!SHOULD_UNPACK!"=="1" (
|
||||
if exist "%ROOT_DIR%\data.dts" (
|
||||
echo [Pre-Setup] Unpacking data.dts to data\
|
||||
pushd "%ROOT_DIR%"
|
||||
"%UNPACKER%" -o "data" "data.dts"
|
||||
if !errorlevel! neq 0 (
|
||||
echo [Pre-Setup] ERROR: Unpack failed. Continuing.
|
||||
)
|
||||
popd
|
||||
) else (
|
||||
echo [Pre-Setup] Skipping unpack: data.dts missing.
|
||||
)
|
||||
|
||||
REM Step 2: Create Patch (once)
|
||||
if not exist "%ROOT_DIR%\patch\" (
|
||||
if exist "%ROOT_DIR%\data\project.dat" (
|
||||
echo [Pre-Setup] Creating patch folder from data\project.dat
|
||||
pushd "%ROOT_DIR%"
|
||||
"%UNPACKER%" ".\data\project.dat" -c
|
||||
if !errorlevel! neq 0 (
|
||||
echo [Pre-Setup] ERROR: Create Patch failed. Continuing.
|
||||
)
|
||||
popd
|
||||
) else (
|
||||
echo [Pre-Setup] Skipping create patch: data\project.dat not found.
|
||||
)
|
||||
)
|
||||
) else (
|
||||
REM SHOULD_UNPACK=0: only Step 2 may still be needed
|
||||
if not exist "%ROOT_DIR%\patch\" (
|
||||
if exist "%ROOT_DIR%\data\project.dat" (
|
||||
echo [Pre-Setup] Creating patch folder from data\project.dat
|
||||
pushd "%ROOT_DIR%"
|
||||
"%UNPACKER%" ".\data\project.dat" -c
|
||||
if !errorlevel! neq 0 (
|
||||
echo [Pre-Setup] ERROR: Create Patch failed. Continuing.
|
||||
)
|
||||
popd
|
||||
) else (
|
||||
echo [Pre-Setup] Skipping create patch: data\project.dat not found.
|
||||
)
|
||||
)
|
||||
)
|
||||
) else (
|
||||
echo [Pre-Setup] SRPG_Unpacker.exe not found; skipping data setup.
|
||||
)
|
||||
)
|
||||
|
||||
:download_extract
|
||||
|
||||
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 Already 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 1
|
||||
)
|
||||
echo Applying patch...
|
||||
REM Files merged by Copy-Item above
|
||||
REM --------------------------------------------------------
|
||||
REM POST-APPLY: Run Steps 3 and 4 after patch files are merged
|
||||
REM 3) Apply Patch to data\project.dat
|
||||
REM 4) Pack data back into data.dts
|
||||
REM --------------------------------------------------------
|
||||
set "UNPACKER=%ROOT_DIR%\SRPG_Unpacker.exe"
|
||||
if exist "%ROOT_DIR%\data.dts" (
|
||||
if exist "%UNPACKER%" (
|
||||
REM Step 3: Apply Patch
|
||||
if exist "%ROOT_DIR%\data\project.dat" (
|
||||
echo Applying patch to data\project.dat...
|
||||
pushd "%ROOT_DIR%"
|
||||
"%UNPACKER%" ".\data\project.dat" -a
|
||||
if !errorlevel! neq 0 (
|
||||
echo ERROR: Apply Patch failed.
|
||||
)
|
||||
popd
|
||||
) else (
|
||||
echo ERROR: data\project.dat not found; cannot apply patch.
|
||||
)
|
||||
|
||||
REM Step 4: Pack
|
||||
if exist "%ROOT_DIR%\data\" (
|
||||
echo Packing data folder to data.dts...
|
||||
pushd "%ROOT_DIR%"
|
||||
"%UNPACKER%" -o "data.dts" "data"
|
||||
if !errorlevel! neq 0 (
|
||||
echo WARNING: Pack failed.
|
||||
)
|
||||
popd
|
||||
) else (
|
||||
echo Step 4: Skipping pack - data folder not found.
|
||||
)
|
||||
) else (
|
||||
echo SRPG_Unpacker.exe not found in root; skipping SRPG patch steps.
|
||||
)
|
||||
)
|
||||
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"
|
||||
endlocal
|
||||
pause
|
||||
exit /b
|
||||
444
gameupdate/gameupdate/patch.ps1
Normal file
444
gameupdate/gameupdate/patch.ps1
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
#Requires -Version 5.1
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$GameRoot
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not $PSBoundParameters.ContainsKey('GameRoot') -or [string]::IsNullOrWhiteSpace($GameRoot)) {
|
||||
$GameRoot = (Get-Location).ProviderPath
|
||||
}
|
||||
else {
|
||||
$GameRoot = (Resolve-Path -LiteralPath $GameRoot).ProviderPath
|
||||
}
|
||||
|
||||
$PatchBundleRoot = $PSScriptRoot
|
||||
|
||||
function Wait-ConsolePause {
|
||||
$null = cmd /c pause
|
||||
}
|
||||
|
||||
function Write-BannerWrongFolder {
|
||||
Write-Host ''
|
||||
Write-Host '========================================'
|
||||
Write-Host 'ERROR: Wrong game root folder!'
|
||||
Write-Host '========================================'
|
||||
Write-Host ''
|
||||
Write-Host 'Game root cannot be the gameupdate folder itself.'
|
||||
Write-Host 'Run GameUpdate.bat from the game root (same folder as GameUpdate.bat).'
|
||||
Write-Host '========================================'
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
function Test-WrongWorkingDirectory {
|
||||
param([string]$Root)
|
||||
return ($Root -and ((Split-Path -Leaf $Root) -ieq 'gameupdate'))
|
||||
}
|
||||
|
||||
function Read-PatchConfig {
|
||||
param([string]$ConfigPath)
|
||||
$cfg = @{ username = ''; repo = ''; branch = '' }
|
||||
if (-not (Test-Path -LiteralPath $ConfigPath)) {
|
||||
return $cfg
|
||||
}
|
||||
foreach ($rawLine in Get-Content -LiteralPath $ConfigPath) {
|
||||
$line = $rawLine.Trim()
|
||||
if (-not $line -or $line.StartsWith('#')) { continue }
|
||||
$eq = $line.IndexOf('=')
|
||||
if ($eq -lt 1) { continue }
|
||||
$k = $line.Substring(0, $eq).Trim().ToLowerInvariant()
|
||||
$v = if ($eq -lt $line.Length - 1) { $line.Substring($eq + 1).Trim() } else { '' }
|
||||
switch ($k) {
|
||||
'username' { $cfg.username = $v }
|
||||
'repo' { $cfg.repo = $v }
|
||||
'branch' { $cfg.branch = $v }
|
||||
}
|
||||
}
|
||||
return $cfg
|
||||
}
|
||||
|
||||
function Invoke-SrpgUnpacker {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ExePath,
|
||||
[Parameter(Mandatory = $true)][string]$WorkingDirectory,
|
||||
[Parameter(Mandatory = $true)][string[]]$Arguments
|
||||
)
|
||||
Push-Location -LiteralPath $WorkingDirectory
|
||||
try {
|
||||
& $ExePath @Arguments
|
||||
return $LASTEXITCODE
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-SrpgPreSetup {
|
||||
param([string]$Root)
|
||||
$unpacker = Join-Path $Root 'SRPG_Unpacker.exe'
|
||||
$dts = Join-Path $Root 'data.dts'
|
||||
$dataDir = Join-Path $Root 'data'
|
||||
$projectDat = Join-Path $dataDir 'project.dat'
|
||||
$patchDir = Join-Path $Root 'patch'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $dts)) {
|
||||
return
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $unpacker)) {
|
||||
Write-Host '[Pre-Setup] SRPG_Unpacker.exe not found; skipping data setup.'
|
||||
return
|
||||
}
|
||||
|
||||
$shouldUnpack =
|
||||
-not (Test-Path -LiteralPath $dataDir) -or
|
||||
-not (Test-Path -LiteralPath $projectDat)
|
||||
|
||||
$runUnpackBlock = {
|
||||
if (Test-Path -LiteralPath $dts) {
|
||||
Write-Host '[Pre-Setup] Unpacking data.dts to data\'
|
||||
$code = Invoke-SrpgUnpacker -ExePath $unpacker -WorkingDirectory $Root -Arguments @('-o', 'data', 'data.dts')
|
||||
if ($code -ne 0) {
|
||||
Write-Host '[Pre-Setup] ERROR: Unpack failed. Continuing.'
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host '[Pre-Setup] Skipping unpack: data.dts missing.'
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $patchDir)) {
|
||||
if (Test-Path -LiteralPath $projectDat) {
|
||||
Write-Host '[Pre-Setup] Creating patch folder from data\project.dat'
|
||||
$code = Invoke-SrpgUnpacker -ExePath $unpacker -WorkingDirectory $Root -Arguments @('.\data\project.dat', '-c')
|
||||
if ($code -ne 0) {
|
||||
Write-Host '[Pre-Setup] ERROR: Create Patch failed. Continuing.'
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host '[Pre-Setup] Skipping create patch: data\project.dat not found.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldUnpack) {
|
||||
& $runUnpackBlock
|
||||
}
|
||||
else {
|
||||
if (-not (Test-Path -LiteralPath $patchDir)) {
|
||||
if (Test-Path -LiteralPath $projectDat) {
|
||||
Write-Host '[Pre-Setup] Creating patch folder from data\project.dat'
|
||||
$code = Invoke-SrpgUnpacker -ExePath $unpacker -WorkingDirectory $Root -Arguments @('.\data\project.dat', '-c')
|
||||
if ($code -ne 0) {
|
||||
Write-Host '[Pre-Setup] ERROR: Create Patch failed. Continuing.'
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host '[Pre-Setup] Skipping create patch: data\project.dat not found.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$prevPref = $ProgressPreference
|
||||
try {
|
||||
$ProgressPreference = 'Continue'
|
||||
Invoke-WebRequest -Uri $Url -OutFile $OutFile -Headers $Headers -UseBasicParsing
|
||||
}
|
||||
finally {
|
||||
$ProgressPreference = $prevPref
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-FileOrDirectoryViaCmd {
|
||||
param([Parameter(Mandatory = $true)][string]$LiteralPath)
|
||||
if (-not (Test-Path -LiteralPath $LiteralPath)) {
|
||||
return
|
||||
}
|
||||
$full = (Resolve-Path -LiteralPath $LiteralPath).ProviderPath
|
||||
$item = Get-Item -LiteralPath $LiteralPath
|
||||
if ($item.PSIsContainer) {
|
||||
cmd.exe /c "rd /s /q `"$full`"" | Out-Null
|
||||
}
|
||||
else {
|
||||
cmd.exe /c "del /f /q `"$full`"" | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-DownloadedRepoZip {
|
||||
param([Parameter(Mandatory = $true)][string]$LiteralPath)
|
||||
Remove-FileOrDirectoryViaCmd -LiteralPath $LiteralPath
|
||||
}
|
||||
|
||||
function Invoke-PatchDownloadExtract {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Root,
|
||||
[Parameter(Mandatory = $true)][string]$StateFilePath,
|
||||
[Parameter(Mandatory = $true)][string]$Username,
|
||||
[Parameter(Mandatory = $true)][string]$Repo,
|
||||
[Parameter(Mandatory = $true)][string]$Branch
|
||||
)
|
||||
|
||||
if ($PSVersionTable.PSEdition -ne 'Core') {
|
||||
try {
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
Set-Location -LiteralPath $Root
|
||||
|
||||
$id = [uri]::EscapeDataString($Username + '/' + $Repo)
|
||||
$branchEnc = [uri]::EscapeDataString($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/$branchEnc") -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 $StateFilePath) {
|
||||
$previousSha = (Get-Content -LiteralPath $StateFilePath -Raw).Trim()
|
||||
}
|
||||
else {
|
||||
Write-Host 'First run: comparing with remote...'
|
||||
}
|
||||
if ($previousSha -eq $latestSha) {
|
||||
return 10
|
||||
}
|
||||
|
||||
Write-Host 'Downloading patch...'
|
||||
$zipPath = Join-Path $PWD.Path 'repo.zip'
|
||||
$stage = Join-Path ([IO.Path]::GetTempPath()) ('gu_' + [Guid]::NewGuid().ToString('N'))
|
||||
$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-FileOrDirectoryViaCmd -LiteralPath $stage
|
||||
}
|
||||
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
|
||||
Write-Host 'Removing downloaded archive (repo.zip)...'
|
||||
Remove-DownloadedRepoZip -LiteralPath $zipPath
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $stage) {
|
||||
Remove-FileOrDirectoryViaCmd -LiteralPath $stage
|
||||
}
|
||||
}
|
||||
|
||||
Set-Content -LiteralPath $StateFilePath -Value $latestSha -Encoding Ascii
|
||||
return 0
|
||||
}
|
||||
|
||||
function Invoke-SrpgPostApply {
|
||||
param([string]$Root)
|
||||
$unpacker = Join-Path $Root 'SRPG_Unpacker.exe'
|
||||
$dts = Join-Path $Root 'data.dts'
|
||||
$projectDat = Join-Path $Root 'data\project.dat'
|
||||
$dataDir = Join-Path $Root 'data'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $dts)) {
|
||||
return
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $unpacker)) {
|
||||
Write-Host 'SRPG_Unpacker.exe not found in root; skipping SRPG patch steps.'
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $projectDat) {
|
||||
Write-Host 'Applying patch to data\project.dat...'
|
||||
$code = Invoke-SrpgUnpacker -ExePath $unpacker -WorkingDirectory $Root -Arguments @('.\data\project.dat', '-a')
|
||||
if ($code -ne 0) {
|
||||
Write-Host 'ERROR: Apply Patch failed.'
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host 'ERROR: data\project.dat not found; cannot apply patch.'
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $dataDir) {
|
||||
Write-Host 'Packing data folder to data.dts...'
|
||||
$code = Invoke-SrpgUnpacker -ExePath $unpacker -WorkingDirectory $Root -Arguments @('-o', 'data.dts', 'data')
|
||||
if ($code -ne 0) {
|
||||
Write-Host 'WARNING: Pack failed.'
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host 'Step 4: Skipping pack - data folder not found.'
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-PatchTempArtifacts {
|
||||
param([string]$Root)
|
||||
$zipPath = Join-Path $Root 'repo.zip'
|
||||
$legacyTmp = Join-Path $Root '_patch_extract_tmp'
|
||||
|
||||
if (Test-Path -LiteralPath $zipPath) {
|
||||
Write-Host ' Removing repo.zip - large files can sit here for minutes if antivirus is scanning.'
|
||||
Remove-FileOrDirectoryViaCmd -LiteralPath $zipPath
|
||||
}
|
||||
if (Test-Path -LiteralPath $legacyTmp) {
|
||||
Write-Host ' Removing _patch_extract_tmp - using rd /s /q (avoids PowerShell hangs on huge folders).'
|
||||
Remove-FileOrDirectoryViaCmd -LiteralPath $legacyTmp
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (Test-WrongWorkingDirectory -Root $GameRoot) {
|
||||
Write-BannerWrongFolder
|
||||
Wait-ConsolePause
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Root: $GameRoot"
|
||||
|
||||
$configPath = Join-Path $PatchBundleRoot 'patch-config.txt'
|
||||
if (-not (Test-Path -LiteralPath $configPath)) {
|
||||
Write-Host 'patch-config.txt not found next to patch.ps1 - skipping patch.'
|
||||
Wait-ConsolePause
|
||||
exit 0
|
||||
}
|
||||
|
||||
$cfg = Read-PatchConfig -ConfigPath $configPath
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($cfg.username)) {
|
||||
Write-Host "ERROR: 'username=' is missing in patch-config.txt"
|
||||
Wait-ConsolePause
|
||||
exit 1
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($cfg.repo)) {
|
||||
Write-Host "ERROR: 'repo=' is missing in patch-config.txt"
|
||||
Wait-ConsolePause
|
||||
exit 1
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($cfg.branch)) {
|
||||
Write-Host "ERROR: 'branch=' is missing in patch-config.txt"
|
||||
Wait-ConsolePause
|
||||
exit 1
|
||||
}
|
||||
|
||||
Invoke-SrpgPreSetup -Root $GameRoot
|
||||
|
||||
$stateFile = Join-Path $PatchBundleRoot 'previous_patch_sha.txt'
|
||||
|
||||
$patchDlExit = 1
|
||||
try {
|
||||
$patchDlExit = Invoke-PatchDownloadExtract -Root $GameRoot -StateFilePath $stateFile `
|
||||
-Username $cfg.username -Repo $cfg.repo -Branch $cfg.branch
|
||||
}
|
||||
catch {
|
||||
Write-Host ''
|
||||
Write-Host $_.Exception.Message
|
||||
if ($_.InvocationInfo.PositionMessage) { Write-Host $_.InvocationInfo.PositionMessage }
|
||||
Remove-PatchTempArtifacts -Root $GameRoot
|
||||
Wait-ConsolePause
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($patchDlExit -eq 10) {
|
||||
Write-Host 'Already up to date.'
|
||||
Wait-ConsolePause
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($patchDlExit -ne 0) {
|
||||
Write-Host 'Download or extraction failed!'
|
||||
Remove-PatchTempArtifacts -Root $GameRoot
|
||||
Wait-ConsolePause
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host 'Applying patch...'
|
||||
|
||||
Invoke-SrpgPostApply -Root $GameRoot
|
||||
|
||||
Write-Host 'Cleaning up...'
|
||||
Remove-PatchTempArtifacts -Root $GameRoot
|
||||
Write-Host 'Done.'
|
||||
|
||||
Wait-ConsolePause
|
||||
exit 0
|
||||
}
|
||||
catch {
|
||||
Write-Host ''
|
||||
Write-Host $_.Exception.Message
|
||||
Wait-ConsolePause
|
||||
exit 1
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ if [ -f "$ROOT_DIR/data.dts" ]; then
|
|||
if [ -f "$UNPACKER" ]; then
|
||||
echo "[Pre-Setup] Running SRPG_Unpacker preparation steps..."
|
||||
|
||||
# Step 1: Unpack (once) — mirror patch.bat: unpack if no data/ or no data/project.dat
|
||||
# Step 1: Unpack (once) — mirror patch.ps1: unpack if no data/ or no data/project.dat
|
||||
if [ ! -d "$ROOT_DIR/data" ] || [ ! -f "$ROOT_DIR/data/project.dat" ]; then
|
||||
if [ -f "$ROOT_DIR/data.dts" ]; then
|
||||
echo "[Pre-Setup] Step 1: Unpacking data.dts -> data"
|
||||
|
|
|
|||
Loading…
Reference in a new issue