Merge branch lewd-hypnosis-city-hypnosm:main into main

This commit is contained in:
RenNyan 2026-05-10 20:00:45 +00:00
commit 732ef9682f
7 changed files with 594 additions and 417 deletions

View file

@ -1,9 +1,69 @@
@echo off @echo off
setlocal setlocal EnableExtensions EnableDelayedExpansion
REM Copy patch.bat to a temp name so the live patch.bat can be overwritten during updates REM When deployed on a game: this file sits in the game root; patch scripts live in .\gameupdate\
copy /Y "gameupdate\patch.bat" "gameupdate\patch2.bat" >nul 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.
call "gameupdate\patch2.bat" set "GU_ROOT=%~dp0"
del /Q "gameupdate\patch2.bat" >nul 2>&1 REM Game root is this batch file's folder (not %%CD%%, so full-path launches still work).
set "GAME_ROOT=!GU_ROOT!"
endlocal if "!GAME_ROOT:~-1!"=="\" set "GAME_ROOT=!GAME_ROOT:~0,-1!"
set "PATCH_SCRIPT_DIR=!GU_ROOT!gameupdate"
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!

View file

@ -3,11 +3,12 @@
# Enable error handling # Enable error handling
set -e set -e
# Copy patch.sh to a new file in gameupdate # Same layout as GameUpdate.bat: patch assets live under ./gameupdate/ next to this file.
cp "gameupdate/patch.sh" "gameupdate/patch2.sh" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GAME_ROOT="$SCRIPT_DIR"
PATCH_DIR="$SCRIPT_DIR/gameupdate"
# Run the new file cd "$GAME_ROOT"
bash "gameupdate/patch2.sh" cp "$PATCH_DIR/patch.sh" "$PATCH_DIR/patch2.sh"
bash "$PATCH_DIR/patch2.sh"
# Delete the new file rm "$PATCH_DIR/patch2.sh"
rm "gameupdate/patch2.sh"

View file

@ -4,10 +4,18 @@
3. Extract to game folder and Replace All. 3. Extract to game folder and Replace All.
## Future Patching ## Future Patching
1. Run GAMEUPDATE.bat to auto patch. 1. Run **`GameUpdate.bat`** to auto patch (Windows).
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. ### Folder layout
4. Optional: set `GAMEUPDATE_DL_ATTEMPTS` (default `2`) to control retries for API checks/downloads. Lower values fail faster; higher values tolerate flaky networks.
**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 # Troubleshooting
**GAMEUPDATE.bat doesn't update and closes immediately** **GAMEUPDATE.bat doesn't update and closes immediately**

View file

@ -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

View file

@ -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

504
gameupdate/patch.ps1 Normal file
View file

@ -0,0 +1,504 @@
#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 {
Write-Host ''
Write-Host 'Press any key to close this window...'
try {
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
}
catch {
Read-Host 'Press Enter to close'
}
}
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 Get-InvalidZipDownloadHint {
param([Parameter(Mandatory = $true)][string]$LiteralPath)
$item = Get-Item -LiteralPath $LiteralPath -ErrorAction SilentlyContinue
if (-not $item) {
return '(downloaded file missing)'
}
$len = $item.Length
$parts = [System.Collections.Generic.List[string]]::new()
$parts.Add("File size: $len bytes.")
if ($len -eq 0) {
$parts.Add('Download was empty.')
return ($parts -join ' ')
}
$peekSize = [Math]::Min([int64]$len, 6144)
$buf = New-Object byte[] $peekSize
$fs = [System.IO.File]::OpenRead($LiteralPath)
try {
[void]$fs.Read($buf, 0, $peekSize)
}
finally {
$fs.Dispose()
}
$text = [System.Text.Encoding]::UTF8.GetString($buf)
if ($text -match '(?i)<!DOCTYPE\s+html|<html[\s>]') {
$parts.Add('Body looks like HTML (often an error page or block). Check username/repo/branch in patch-config.txt and network.')
}
elseif ($text.TrimStart().StartsWith('{')) {
try {
$j = ($text.TrimStart() | ConvertFrom-Json)
if ($j.message) {
$parts.Add("GitLab JSON: $($j.message)")
}
else {
$parts.Add('Body starts with JSON (likely an API error, not a ZIP).')
}
}
catch {
$parts.Add('Body starts with `{` but was not parseable JSON (truncated or wrong encoding).')
}
}
else {
$flat = ($text -replace '[\r\n]+', ' ')
$take = [Math]::Min(200, $flat.Length)
if ($take -gt 0) {
$parts.Add(('UTF-8 preview: ' + $flat.Substring(0, $take)))
}
}
return ($parts -join ' ')
}
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)
$headers = @{ 'User-Agent' = 'DazedMTL-Patcher-1.0' }
$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) {
$hint = Get-InvalidZipDownloadHint -LiteralPath $zipPath
throw "PATCH_ERR:ZIP:Download is not a valid ZIP (missing PK header). $hint"
}
}
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
}

View file

@ -102,7 +102,7 @@ if [ -f "$ROOT_DIR/data.dts" ]; then
if [ -f "$UNPACKER" ]; then if [ -f "$UNPACKER" ]; then
echo "[Pre-Setup] Running SRPG_Unpacker preparation steps..." 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 [ ! -d "$ROOT_DIR/data" ] || [ ! -f "$ROOT_DIR/data/project.dat" ]; then
if [ -f "$ROOT_DIR/data.dts" ]; then if [ -f "$ROOT_DIR/data.dts" ]; then
echo "[Pre-Setup] Step 1: Unpacking data.dts -> data" echo "[Pre-Setup] Step 1: Unpacking data.dts -> data"