New patcher

This commit is contained in:
DazedAnon 2026-07-09 17:23:05 -05:00
parent 984cfa0b2c
commit fa15db7c01
13 changed files with 657 additions and 58 deletions

3
.gitignore vendored
View file

@ -21,6 +21,7 @@
!.gitignore
!README.md
!patch-config.txt
!patch-config.example.txt
!GameUpdate*
!patch*
!Game.dat
@ -29,6 +30,8 @@
!package.nw
!SRPG_Unpacker.exe
!(SRPG_Unpacker Patcher).bat
!UberWolfCli.exe
!UberWolfCli.LICENSE.txt
!Data/BasicData/CommonEvent.dat
!Data/BasicData/DataBase.dat
!Data/BasicData/CDataBase.dat

View file

@ -17,22 +17,67 @@
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.
### patch-config.txt
Create `gameupdate/patch-config.txt` next to `patch.ps1` (see `patch-config.example.txt`).
In DazedMTLTool, set **Config → Game Update Defaults** (forge, host, org/username, branch) once. Step 1 **Copy gameupdate/** writes those into each game's `patch-config.txt`. You still set `repo=` per game.
```txt
forge=gitlab
host=gitgud.io
username=YOUR_ORG_OR_USER
repo=YOUR_PATCH_REPO
branch=main
```
| Key | Meaning |
|-----|---------|
| `forge` | `gitlab` (default), `github`, or `forgejo` (`gitea` also accepted) |
| `host` | Hostname only. Defaults: `gitgud.io` / `github.com` / `codeberg.org` |
| `username` | Owner / org / group (`owner=` / `org=` aliases work) |
| `repo` | Repository name |
| `branch` | Branch to track |
Examples:
```txt
# GitLab / gitgud
forge=gitlab
host=gitgud.io
username=myorg
repo=cool-game-en
branch=main
# GitHub
forge=github
host=github.com
username=myorg
repo=cool-game-en
branch=main
# Forgejo / Codeberg
forge=forgejo
host=codeberg.org
username=myorg
repo=cool-game-en
branch=main
```
Older configs with only `username` / `repo` / `branch` still work (assumes GitLab on `gitgud.io`).
# Troubleshooting
**GAMEUPDATE.bat doesn't update and closes immediately**
1. Make sure your path doesn't contain any Japanese characters or lots of whitespace.
2. Make sure you actually have permissions in the folder
3. Auto-update calls GitLabs public HTTP API (`/api/v4/...`), not the web “Download ZIP” URL—no account or token is required for public patch repos.
For WOLF RPG games, if you downloaded the game off of DLSite, you will need to do some extra steps to patch it. This is because there is a "master" file called Data.wolf that will take priority over the english patch files. You will need this file to be a folder before patching will work.
3. Auto-update calls the forge's public HTTP API (GitLab `/api/v4`, GitHub `/repos/...`, Forgejo `/api/v1`), not the web “Download ZIP” button - no account is required for public patch repos.
# Wolf Games
1) Download the latest UberWolf.exe release from the following link:
https://github.com/Sinflower/UberWolf/releases
2) Drag Data.wolf onto UberWolf.exe. This will create a new folder called data.wolf~
3) Rename the new data.wolf~ folder to Data
4) Delete the Data.wolf file
5) Delete previous_patch_sha.txt (this will exist if you ran GameUpdate.bat previously)
6) Run GameUpdate.bat
WOLF RPG installs from DLSite ship a master `Data.wolf` archive that takes priority over loose English patch files. `GameUpdate.bat` detects that and unpacks it automatically with the bundled `UberWolfCli.exe` (MIT, [Sinflower/UberWolf](https://github.com/Sinflower/UberWolf)), then renames `Data.wolf` to `Data.wolf.bak` so the patched loose `Data/` files load.
Just run **`GameUpdate.bat`**. No manual UberWolf download is required.
If unpack fails (rare Pro / protected builds), unpack once with [UberWolf](https://github.com/Sinflower/UberWolf/releases), ensure a loose `Data/` folder exists, rename or remove `Data.wolf`, delete `gameupdate/previous_patch_sha.txt`, and run `GameUpdate.bat` again.
# Edit/Contribute
TLDR 3 steps.

21
UberWolfCli.LICENSE.txt Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Sinflower
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
UberWolfCli.exe Normal file

Binary file not shown.

View file

@ -0,0 +1,28 @@
# GameUpdate patch source (copy to patch-config.txt and edit).
# Lines starting with # are comments. Keys are case-insensitive.
#
# Tip: set forge / host / username / branch once in DazedMTLTool
# Config → Game Update Defaults. Step 1 "Copy gameupdate/" writes
# those into this file automatically (you still set repo= per game).
# Where the patch repo lives:
# gitlab - GitLab / gitgud.io / self-hosted GitLab (default)
# github - GitHub.com or GitHub Enterprise
# forgejo - Forgejo (also accepts gitea)
forge=gitlab
# Host only (no https://). Defaults:
# gitlab -> gitgud.io
# github -> github.com
# forgejo -> codeberg.org
# Examples: gitgud.io gitlab.com github.com codeberg.org git.example.com
host=gitgud.io
# Owner / org / group that owns the repo (set in Config; rarely changes)
username=YOUR_ORG_OR_USER
# Repository name (per game)
repo=YOUR_PATCH_REPO
# Branch to track
branch=main

View file

@ -1,3 +1,7 @@
username=dazed-translations
repo=magical-girls-runa-and-nanami
branch=main
# Generated by DazedMTLTool from Config → Game Update defaults.
# Edit repo= per game. forge/host/username usually stay the same.
forge=forgejo
host=git.dazedtl.dev
username=dazed-translations
repo=YOUR_PATCH_REPO
branch=main

View file

@ -194,7 +194,13 @@ function Test-WrongWorkingDirectory {
function Read-PatchConfig {
param([string]$ConfigPath)
$cfg = @{ username = ''; repo = ''; branch = '' }
$cfg = @{
forge = 'gitlab'
host = ''
username = ''
repo = ''
branch = ''
}
if (-not (Test-Path -LiteralPath $ConfigPath)) {
return $cfg
}
@ -206,7 +212,12 @@ function Read-PatchConfig {
$k = $line.Substring(0, $eq).Trim().ToLowerInvariant()
$v = if ($eq -lt $line.Length - 1) { $line.Substring($eq + 1).Trim() } else { '' }
switch ($k) {
'forge' { $cfg.forge = $v }
'provider' { $cfg.forge = $v }
'host' { $cfg.host = $v }
'username' { $cfg.username = $v }
'owner' { $cfg.username = $v }
'org' { $cfg.username = $v }
'repo' { $cfg.repo = $v }
'branch' { $cfg.branch = $v }
}
@ -214,6 +225,115 @@ function Read-PatchConfig {
return $cfg
}
function Resolve-PatchForge {
param([Parameter(Mandatory = $true)][hashtable]$Cfg)
$raw = ([string]$Cfg.forge).Trim().ToLowerInvariant()
if ([string]::IsNullOrWhiteSpace($raw)) { $raw = 'gitlab' }
$kind = switch -Regex ($raw) {
'^(gitlab|gl|gitgud)$' { 'gitlab' }
'^(github|gh)$' { 'github' }
'^(forgejo|gitea|fj|codeberg)$' { 'forgejo' }
default { $null }
}
if (-not $kind) {
throw ("PATCH_ERR:CONFIG:Unknown forge='{0}'. Use gitlab, github, or forgejo." -f $Cfg.forge)
}
$hostName = ([string]$Cfg.host).Trim()
$hostName = $hostName -replace '^https?://', ''
$hostName = $hostName.TrimEnd('/')
if ($hostName.Contains('/')) {
$hostName = $hostName.Split('/')[0]
}
if ([string]::IsNullOrWhiteSpace($hostName)) {
$hostName = switch ($kind) {
'gitlab' { 'gitgud.io' }
'github' { 'github.com' }
'forgejo' { 'codeberg.org' }
}
}
$webBase = "https://$hostName"
$apiBase = switch ($kind) {
'gitlab' { "$webBase/api/v4" }
'github' {
if ($hostName -eq 'github.com') { 'https://api.github.com' }
else { "$webBase/api/v3" }
}
'forgejo' { "$webBase/api/v1" }
}
return @{
kind = $kind
host = $hostName
webBase = $webBase
apiBase = $apiBase
username = ([string]$Cfg.username).Trim()
repo = ([string]$Cfg.repo).Trim()
branch = ([string]$Cfg.branch).Trim()
}
}
function New-PatchApiHeaders {
param([Parameter(Mandatory = $true)][hashtable]$Resolved)
$headers = @{ 'User-Agent' = 'DazedMTL-Patcher-1.0' }
if ($Resolved.kind -eq 'github') {
$headers['Accept'] = 'application/vnd.github+json'
}
return $headers
}
function Get-PatchLatestSha {
param(
[Parameter(Mandatory = $true)][hashtable]$Resolved,
[Parameter(Mandatory = $true)][hashtable]$Headers
)
$ownerEnc = [uri]::EscapeDataString($Resolved.username)
$repoEnc = [uri]::EscapeDataString($Resolved.repo)
$branchEnc = [uri]::EscapeDataString($Resolved.branch)
switch ($Resolved.kind) {
'gitlab' {
$id = [uri]::EscapeDataString($Resolved.username + '/' + $Resolved.repo)
$uri = "$($Resolved.apiBase)/projects/$id/repository/branches/$branchEnc"
return [string](Invoke-RestMethod -Uri $uri -Headers $Headers).commit.id
}
'github' {
$uri = "$($Resolved.apiBase)/repos/$ownerEnc/$repoEnc/commits/$branchEnc"
return [string](Invoke-RestMethod -Uri $uri -Headers $Headers).sha
}
'forgejo' {
$uri = "$($Resolved.apiBase)/repos/$ownerEnc/$repoEnc/branches/$branchEnc"
return [string](Invoke-RestMethod -Uri $uri -Headers $Headers).commit.id
}
}
}
function Get-PatchArchiveUrl {
param(
[Parameter(Mandatory = $true)][hashtable]$Resolved,
[Parameter(Mandatory = $true)][string]$Sha
)
$ownerEnc = [uri]::EscapeDataString($Resolved.username)
$repoEnc = [uri]::EscapeDataString($Resolved.repo)
$shaEnc = [uri]::EscapeDataString($Sha)
switch ($Resolved.kind) {
'gitlab' {
$id = [uri]::EscapeDataString($Resolved.username + '/' + $Resolved.repo)
return "$($Resolved.apiBase)/projects/$id/repository/archive.zip?sha=$shaEnc"
}
'github' {
return "$($Resolved.apiBase)/repos/$ownerEnc/$repoEnc/zipball/$shaEnc"
}
'forgejo' {
return "$($Resolved.apiBase)/repos/$ownerEnc/$repoEnc/archive/$shaEnc.zip"
}
}
}
function Invoke-SrpgUnpacker {
param(
[Parameter(Mandatory = $true)][string]$ExePath,
@ -230,6 +350,140 @@ function Invoke-SrpgUnpacker {
}
}
function Test-WolfLooseDataReady {
param([Parameter(Mandatory = $true)][string]$DataDir)
if (-not (Test-Path -LiteralPath $DataDir -PathType Container)) {
return $false
}
$markers = @(
(Join-Path $DataDir 'BasicData\CommonEvent.dat'),
(Join-Path $DataDir 'CommonEvent.dat')
)
foreach ($marker in $markers) {
if (Test-Path -LiteralPath $marker -PathType Leaf) {
return $true
}
}
if (@(Get-ChildItem -LiteralPath $DataDir -Filter '*.mps' -File -ErrorAction SilentlyContinue).Count -gt 0) {
return $true
}
$basic = Join-Path $DataDir 'BasicData'
if (Test-Path -LiteralPath $basic -PathType Container) {
if (@(Get-ChildItem -LiteralPath $basic -Filter '*.dat' -File -ErrorAction SilentlyContinue).Count -gt 0) {
return $true
}
}
return $false
}
function Find-UberWolfCli {
param([Parameter(Mandatory = $true)][string]$Root)
$candidates = @(
(Join-Path $Root 'UberWolfCli.exe'),
(Join-Path $Root 'gameupdate\UberWolfCli.exe')
)
foreach ($path in $candidates) {
if (Test-Path -LiteralPath $path -PathType Leaf) {
return $path
}
}
return $null
}
function Invoke-WolfPreSetup {
<#
WOLF installs often ship Data.wolf, which overrides loose patched Data/ files.
Unpack once with UberWolfCli (bundled in the patch repo), then retire Data.wolf
so GameUpdate can copy injected .mps/.dat on top.
#>
param([Parameter(Mandatory = $true)][string]$Root)
$dataWolf = $null
foreach ($name in @('Data.wolf', 'data.wolf')) {
$candidate = Join-Path $Root $name
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
$dataWolf = $candidate
break
}
}
if (-not $dataWolf) {
return
}
$dataDir = Join-Path $Root 'Data'
if (-not (Test-Path -LiteralPath $dataDir -PathType Container)) {
$dataDirAlt = Join-Path $Root 'data'
if (Test-Path -LiteralPath $dataDirAlt -PathType Container) {
$dataDir = $dataDirAlt
}
}
if (Test-WolfLooseDataReady -DataDir $dataDir) {
$bak = "$dataWolf.bak"
if (-not (Test-Path -LiteralPath $bak)) {
Write-Host '[Pre-Setup] Loose Data/ already present; retiring Data.wolf -> Data.wolf.bak'
Rename-Item -LiteralPath $dataWolf -NewName ([IO.Path]::GetFileName($bak)) -Force
}
else {
Write-Host '[Pre-Setup] Loose Data/ already present; removing leftover Data.wolf'
Remove-Item -LiteralPath $dataWolf -Force
}
return
}
$cli = Find-UberWolfCli -Root $Root
if (-not $cli) {
Write-Host '[Pre-Setup] Data.wolf found but UberWolfCli.exe is missing.'
Write-Host ' Copy UberWolfCli.exe into the game root (Step 1 gameupdate/) and re-run.'
return
}
Write-Host '[Pre-Setup] Unpacking Data.wolf with UberWolfCli (one-time)...'
$code = Invoke-SrpgUnpacker -ExePath $cli -WorkingDirectory $Root -Arguments @('-o', $dataWolf)
if ($code -ne 0) {
Write-Host ('[Pre-Setup] ERROR: UberWolfCli exited with code {0}.' -f $code)
return
}
# UberWolf writes next to the archive as <stem>/ (Data/). Also accept data.wolf~ leftovers.
$unpacked = Join-Path $Root 'Data'
if (-not (Test-Path -LiteralPath $unpacked -PathType Container)) {
$unpacked = Join-Path $Root 'data'
}
if (-not (Test-WolfLooseDataReady -DataDir $unpacked)) {
foreach ($alt in @('data.wolf~', 'Data.wolf~', 'Data~', 'data~')) {
$altPath = Join-Path $Root $alt
if (Test-Path -LiteralPath $altPath -PathType Container) {
Write-Host ("[Pre-Setup] Promoting {0} -> Data\" -f $alt)
if (Test-Path -LiteralPath (Join-Path $Root 'Data')) {
Remove-FileOrDirectoryViaCmd -LiteralPath (Join-Path $Root 'Data')
}
Rename-Item -LiteralPath $altPath -NewName 'Data' -Force
$unpacked = Join-Path $Root 'Data'
break
}
}
}
if (-not (Test-WolfLooseDataReady -DataDir $unpacked)) {
Write-Host '[Pre-Setup] ERROR: Unpack finished but loose Data/ still looks empty.'
return
}
$bak = "$dataWolf.bak"
if (Test-Path -LiteralPath $dataWolf -PathType Leaf) {
if (-not (Test-Path -LiteralPath $bak)) {
Write-Host '[Pre-Setup] Retiring Data.wolf -> Data.wolf.bak'
Rename-Item -LiteralPath $dataWolf -NewName ([IO.Path]::GetFileName($bak)) -Force
}
else {
Write-Host '[Pre-Setup] Removing Data.wolf so loose Data/ takes priority'
Remove-Item -LiteralPath $dataWolf -Force
}
}
Write-Host '[Pre-Setup] Wolf Data/ is ready for patching.'
}
function Invoke-SrpgPreSetup {
param([string]$Root)
$unpacker = Join-Path $Root 'SRPG_Unpacker.exe'
@ -512,9 +766,7 @@ 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
[Parameter(Mandatory = $true)][hashtable]$Resolved
)
if ($PSVersionTable.PSEdition -ne 'Core') {
@ -526,13 +778,11 @@ function Invoke-PatchDownloadExtract {
Set-Location -LiteralPath $Root
$id = [uri]::EscapeDataString($Username + '/' + $Repo)
$branchEnc = [uri]::EscapeDataString($Branch)
$headers = @{ 'User-Agent' = 'DazedMTL-Patcher-1.0' }
$headers = New-PatchApiHeaders -Resolved $Resolved
$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
Get-PatchLatestSha -Resolved $Resolved -Headers $headers
}
if (-not $latestSha) { throw 'PATCH_ERR:API:Latest commit SHA response was empty.' }
$latestSha = ([string]$latestSha).Trim()
@ -551,8 +801,7 @@ function Invoke-PatchDownloadExtract {
$zipPath = Join-Path $Root ('dazedmtl_patch_' + [Guid]::NewGuid().ToString('N') + '.zip')
Write-Host ('Downloading patch... ' + $zipPath)
$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"
$archiveUrl = Get-PatchArchiveUrl -Resolved $Resolved -Sha $latestSha
$dlSw = [System.Diagnostics.Stopwatch]::StartNew()
Invoke-WithRetry -Name 'Download archive with Invoke-WebRequest' -Attempts $dlAttempts -Action {
@ -599,7 +848,21 @@ function Invoke-PatchDownloadExtract {
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
$stagedRoot = $dirs[0].FullName
# Stage UberWolfCli into the game root before unpack so first-time
# players can convert Data.wolf -> Data/ before English files land.
foreach ($cliName in @('UberWolfCli.exe', 'UberWolfCli.LICENSE.txt')) {
$stagedCli = Join-Path $stagedRoot $cliName
$destCli = Join-Path $Root $cliName
if ((Test-Path -LiteralPath $stagedCli -PathType Leaf) -and
-not (Test-Path -LiteralPath $destCli -PathType Leaf)) {
Copy-Item -LiteralPath $stagedCli -Destination $destCli -Force
}
}
Invoke-WolfPreSetup -Root $Root
Copy-Item -Path (Join-Path $stagedRoot '*') -Destination $PWD.Path -Recurse -Force
}
finally {
if (Test-Path -LiteralPath $zipPath) {
@ -717,8 +980,19 @@ try {
exit 1
}
Write-Host ('Pulling patch from https://gitgud.io/{0}/{1} (branch: {2})' -f $cfg.username, $cfg.repo, $cfg.branch)
try {
$resolved = Resolve-PatchForge -Cfg $cfg
}
catch {
Write-Host $_.Exception.Message
Wait-ConsolePause
exit 1
}
Write-Host ('Pulling patch from {0}/{1}/{2} ({3}, branch: {4})' -f `
$resolved.webBase, $resolved.username, $resolved.repo, $resolved.kind, $resolved.branch)
Invoke-WolfPreSetup -Root $GameRoot
Invoke-SrpgPreSetup -Root $GameRoot
$stateFile = Join-Path $PatchBundleRoot 'previous_patch_sha.txt'
@ -726,7 +1000,7 @@ try {
$patchDlExit = 1
try {
$patchDlExit = Invoke-PatchDownloadExtract -Root $GameRoot -StateFilePath $stateFile `
-Username $cfg.username -Repo $cfg.repo -Branch $cfg.branch
-Resolved $resolved
}
catch {
Write-Host ''
@ -753,6 +1027,9 @@ try {
Write-Host 'Applying patch...'
Invoke-SrpgPostApply -Root $GameRoot
# Safety: if Data/ is ready (from unpack or patch files) but Data.wolf
# remains, retire it so the archive does not override loose English files.
Invoke-WolfPreSetup -Root $GameRoot
Write-Host 'Cleaning up...'
Remove-PatchTempArtifacts -Root $GameRoot

View file

@ -6,7 +6,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(pwd)"
CONFIG_FILE="$SCRIPT_DIR/patch-config.txt"
STATE_FILE="$SCRIPT_DIR/previous_patch_sha.txt"
GITGUD_API="https://gitgud.io/api/v4"
API_HEADERS=( -H "User-Agent: GameUpdate/1.0" )
check_dependency() {
@ -38,19 +37,113 @@ echo "Config file path: $CONFIG_FILE"
# shellcheck disable=SC1090
. "$CONFIG_FILE"
if [ -z "${username:-}" ]; then
# Defaults for older configs that only set username/repo/branch.
forge="${forge:-${provider:-gitlab}}"
host="${host:-}"
username="${username:-${owner:-${org:-}}}"
repo="${repo:-}"
branch="${branch:-}"
if [ -z "$username" ]; then
echo "ERROR: 'username=' is missing in gameupdate/patch-config.txt"
exit 1
fi
if [ -z "${repo:-}" ]; then
if [ -z "$repo" ]; then
echo "ERROR: 'repo=' is missing in gameupdate/patch-config.txt"
exit 1
fi
if [ -z "${branch:-}" ]; then
if [ -z "$branch" ]; then
echo "ERROR: 'branch=' is missing in gameupdate/patch-config.txt"
exit 1
fi
normalize_forge() {
local raw
raw="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
case "$raw" in
''|gitlab|gl|gitgud) echo gitlab ;;
github|gh) echo github ;;
forgejo|gitea|fj|codeberg) echo forgejo ;;
*)
echo "ERROR: Unknown forge='$1'. Use gitlab, github, or forgejo." >&2
return 1
;;
esac
}
FORGE_KIND="$(normalize_forge "$forge")" || exit 1
# Strip scheme / path from host if a user pasted a full URL.
HOST_NAME="$(printf '%s' "$host" | sed -E 's#^https?://##; s#/.*##; s#[[:space:]]##g')"
if [ -z "$HOST_NAME" ]; then
case "$FORGE_KIND" in
gitlab) HOST_NAME="gitgud.io" ;;
github) HOST_NAME="github.com" ;;
forgejo) HOST_NAME="codeberg.org" ;;
esac
fi
WEB_BASE="https://${HOST_NAME}"
case "$FORGE_KIND" in
gitlab) API_BASE="${WEB_BASE}/api/v4" ;;
github)
if [ "$HOST_NAME" = "github.com" ]; then
API_BASE="https://api.github.com"
else
API_BASE="${WEB_BASE}/api/v3"
fi
;;
forgejo) API_BASE="${WEB_BASE}/api/v1" ;;
esac
API_HEADERS=( -H "User-Agent: GameUpdate/1.0" )
if [ "$FORGE_KIND" = "github" ]; then
API_HEADERS+=( -H "Accept: application/vnd.github+json" )
fi
owner_enc=$(jq -nr --arg s "$username" '$s | @uri')
repo_enc=$(jq -nr --arg s "$repo" '$s | @uri')
branch_enc=$(jq -nr --arg b "$branch" '$b | @uri')
project_enc=$(jq -nr --arg ns "$username" --arg rp "$repo" '$ns + "/" + $rp | @uri')
resolve_latest_sha() {
case "$FORGE_KIND" in
gitlab)
curl -fsSL "${API_HEADERS[@]}" \
"${API_BASE}/projects/${project_enc}/repository/branches/${branch_enc}" \
| jq -r '.commit.id'
;;
github)
curl -fsSL "${API_HEADERS[@]}" \
"${API_BASE}/repos/${owner_enc}/${repo_enc}/commits/${branch_enc}" \
| jq -r '.sha'
;;
forgejo)
curl -fsSL "${API_HEADERS[@]}" \
"${API_BASE}/repos/${owner_enc}/${repo_enc}/branches/${branch_enc}" \
| jq -r '.commit.id'
;;
esac
}
archive_url_for_sha() {
local sha_enc
sha_enc=$(jq -nr --arg s "$1" '$s | @uri')
case "$FORGE_KIND" in
gitlab)
echo "${API_BASE}/projects/${project_enc}/repository/archive.zip?sha=${sha_enc}"
;;
github)
echo "${API_BASE}/repos/${owner_enc}/${repo_enc}/zipball/${sha_enc}"
;;
forgejo)
echo "${API_BASE}/repos/${owner_enc}/${repo_enc}/archive/${sha_enc}.zip"
;;
esac
}
echo "Pulling patch from ${WEB_BASE}/${username}/${repo} (${FORGE_KIND}, branch: ${branch})"
RETRIES="${GAMEUPDATE_DL_ATTEMPTS:-2}"
if ! [[ "$RETRIES" =~ ^[1-9][0-9]*$ ]]; then
RETRIES=2
@ -74,23 +167,139 @@ retry_cmd() {
done
}
project_enc=$(jq -nr --arg ns "$username" --arg rp "$repo" '$ns + "/" + $rp | @uri')
branch_enc=$(jq -nr --arg b "$branch" '$b | @uri')
# Get the latest hash
echo "Getting latest commit SHA hash"
latest_patch_sha="$(
retry_cmd "Resolve latest patch SHA" \
curl -fsSL "${API_HEADERS[@]}" \
"${GITGUD_API}/projects/${project_enc}/repository/branches/${branch_enc}" \
| jq -r '.commit.id'
)"
latest_patch_sha=""
attempt=1
while true; do
if latest_patch_sha="$(resolve_latest_sha)"; then
break
fi
if [ "$attempt" -ge "$RETRIES" ]; then
echo "Resolve latest patch SHA failed after $attempt attempt(s)."
exit 1
fi
echo "Resolve latest patch SHA failed ($attempt/$RETRIES), retrying..."
attempt=$((attempt + 1))
sleep 2
done
latest_patch_sha="$(printf '%s' "$latest_patch_sha" | tr -d '[:space:]')"
if [ -z "$latest_patch_sha" ] || [ "$latest_patch_sha" = "null" ]; then
echo "PATCH_ERR:API:Latest commit SHA response was empty."
exit 1
fi
# --------------------------------------------------------
# PRE-SETUP: Wolf Data.wolf -> loose Data/ (before patch copy)
# --------------------------------------------------------
wolf_loose_data_ready() {
local data_dir="$1"
[ -d "$data_dir" ] || return 1
[ -f "$data_dir/BasicData/CommonEvent.dat" ] && return 0
[ -f "$data_dir/CommonEvent.dat" ] && return 0
if compgen -G "$data_dir/*.mps" > /dev/null 2>&1; then
return 0
fi
if compgen -G "$data_dir/BasicData/*.dat" > /dev/null 2>&1; then
return 0
fi
return 1
}
find_uberwolf_cli() {
if [ -f "$ROOT_DIR/UberWolfCli.exe" ]; then
echo "$ROOT_DIR/UberWolfCli.exe"
return 0
fi
if [ -f "$ROOT_DIR/gameupdate/UberWolfCli.exe" ]; then
echo "$ROOT_DIR/gameupdate/UberWolfCli.exe"
return 0
fi
return 1
}
retire_data_wolf() {
local data_wolf="$1"
local bak="${data_wolf}.bak"
if [ ! -f "$data_wolf" ]; then
return 0
fi
if [ ! -e "$bak" ]; then
echo "[Pre-Setup] Retiring $(basename "$data_wolf") -> $(basename "$bak")"
mv -f "$data_wolf" "$bak"
else
echo "[Pre-Setup] Removing leftover $(basename "$data_wolf") so loose Data/ takes priority"
rm -f "$data_wolf"
fi
}
invoke_wolf_pre_setup() {
local data_wolf=""
local name
for name in Data.wolf data.wolf; do
if [ -f "$ROOT_DIR/$name" ]; then
data_wolf="$ROOT_DIR/$name"
break
fi
done
[ -n "$data_wolf" ] || return 0
local data_dir="$ROOT_DIR/Data"
if [ ! -d "$data_dir" ] && [ -d "$ROOT_DIR/data" ]; then
data_dir="$ROOT_DIR/data"
fi
if wolf_loose_data_ready "$data_dir"; then
retire_data_wolf "$data_wolf"
return 0
fi
local cli
cli="$(find_uberwolf_cli || true)"
if [ -z "$cli" ]; then
echo "[Pre-Setup] Data.wolf found but UberWolfCli.exe is missing."
echo " Copy UberWolfCli.exe into the game root (Step 1 gameupdate/) and re-run."
return 0
fi
echo "[Pre-Setup] Unpacking Data.wolf with UberWolfCli (one-time)..."
# Native .exe works under Windows / Git Bash; wine covers Linux hosts.
if ( cd "$ROOT_DIR" && "$cli" -o "$data_wolf" ); then
:
elif command -v wine > /dev/null 2>&1 && ( cd "$ROOT_DIR" && wine "$cli" -o "$data_wolf" ); then
:
else
echo "[Pre-Setup] ERROR: UberWolfCli unpack failed."
return 0
fi
local unpacked="$ROOT_DIR/Data"
if ! wolf_loose_data_ready "$unpacked"; then
unpacked="$ROOT_DIR/data"
fi
if ! wolf_loose_data_ready "$unpacked"; then
local alt
for alt in data.wolf~ Data.wolf~ Data~ data~; do
if [ -d "$ROOT_DIR/$alt" ]; then
echo "[Pre-Setup] Promoting $alt -> Data/"
rm -rf "$ROOT_DIR/Data"
mv -f "$ROOT_DIR/$alt" "$ROOT_DIR/Data"
unpacked="$ROOT_DIR/Data"
break
fi
done
fi
if wolf_loose_data_ready "$unpacked"; then
retire_data_wolf "$data_wolf"
echo "[Pre-Setup] Wolf Data/ is ready for patching."
else
echo "[Pre-Setup] ERROR: Unpack finished but loose Data/ still looks empty."
fi
}
invoke_wolf_pre_setup
# --------------------------------------------------------
# PRE-SETUP: Ensure SRPG data and patch structure exists
# Run Steps 1 and 2 BEFORE pulling repo patch to avoid overwriting updates
@ -132,10 +341,10 @@ fi
download_extract() {
echo "Downloading latest patch..."
archive_sha_enc=$(jq -nr --arg s "$latest_patch_sha" '$s | @uri')
archive_url="$(archive_url_for_sha "$latest_patch_sha")"
if ! retry_cmd "Download archive with curl" \
curl -fSL --progress-bar "${API_HEADERS[@]}" \
"${GITGUD_API}/projects/${project_enc}/repository/archive.zip?sha=${archive_sha_enc}" \
"$archive_url" \
-o "$ROOT_DIR/repo.zip"; then
echo "Download failed!"
rm -f "$ROOT_DIR/repo.zip"
@ -166,6 +375,15 @@ download_extract() {
return 1
fi
# Stage UberWolfCli before unpack so Data.wolf -> Data/ happens before
# English patch files are copied on top.
for cli_name in UberWolfCli.exe UberWolfCli.LICENSE.txt; do
if [ -f "$inner/$cli_name" ] && [ ! -f "$ROOT_DIR/$cli_name" ]; then
cp -f "$inner/$cli_name" "$ROOT_DIR/$cli_name"
fi
done
invoke_wolf_pre_setup
echo "Applying patch..."
if ! cp -r "$inner"/* "$ROOT_DIR/"; then
echo "Patch application failed!"
@ -205,6 +423,9 @@ download_extract() {
fi
fi
# Retire Data.wolf if loose Data/ is ready after the patch copy.
invoke_wolf_pre_setup
echo "$latest_patch_sha" > "$STATE_FILE"
}

View file

@ -2853,4 +2853,4 @@
}
],
"kind": "db"
}
}

View file

@ -17573,4 +17573,4 @@
}
],
"kind": "db"
}
}

View file

@ -33,4 +33,4 @@
],
"defer_groups": [],
"notes": "Recommended order: Names → DB foundation (258 lines) → DB narrative (2,333 lines) → Maps/events."
}
}

View file

@ -1,14 +1,14 @@
{
"nameWraps": [
{
"note": "├■街の噂MOB",
"maxLines": 3,
"width": 30
},
{
"note": "├■ウワサ・MOB進行度等",
"width": 30,
"maxLines": 3
}
]
}
"nameWraps": [
{
"note": "├■街の噂MOB",
"maxLines": 3,
"width": 30
},
{
"note": "├■ウワサ・MOB進行度等",
"width": 30,
"maxLines": 3
}
]
}

View file

@ -28,4 +28,4 @@
"font": 30
}
}
}
}