Wolf Game Update Patcher
This commit is contained in:
parent
69f7ca7316
commit
8f5e62ac75
12 changed files with 921 additions and 42 deletions
|
|
@ -325,7 +325,7 @@ Open the **Workflow** tab and choose **Wolf RPG (WolfDawn)** from the engine sel
|
|||
| Step | Action |
|
||||
|------|--------|
|
||||
| **0 Project** | Select the game root folder (browse or Enter — detection also runs when you reopen the tab). If `wolf_json/` does not exist yet, the tool automatically unpacks `.wolf` archives when needed and extracts text into the game's `wolf_json/` folder (maps, common events, databases, `Game.dat`, Evtext, and `names.json`). A checklist lists every JSON file in `wolf_json/`; tick the ones you want and click **Import** (or leave Step 0 — checked files auto-import into the tool's `files/` folder, matching the RPG Maker workflow). Extraction snapshots pristine binaries into `wolf_json/originals/` for idempotent inject in Step 7. |
|
||||
| **1 Pre-process** | Optional: **dazedformat** normalises JSON in `wolf_json/` and `files/` (`json.dump`, indent 4) for clean git diffs; **Copy gameupdate/** installs the updater scripts, patch scripts, and `.gitignore` into the game root for git-based patching. Paths auto-fill from Step 0. |
|
||||
| **1 Pre-process** | Optional: **dazedformat** normalises JSON in `wolf_json/` and `files/` (`json.dump`, indent 4) for clean git diffs; **Copy gameupdate/** installs the updater scripts, patch scripts, `.gitignore`, and `UberWolfCli.exe` into the game root for git-based patching (players auto-unpack `Data.wolf` on first GameUpdate). Paths auto-fill from Step 0. |
|
||||
| **2 Glossary** | Build `vocab.txt` before translating: copy the WOLF-tailored prompt into Cursor/Copilot with the extracted `files/` JSON, let it discover character names, speech registers, and lore terms, then paste the result into the in-tab editor and save. Item/skill/enemy value names (`names.json`) are translated in Step 3 and harvested into `vocab.txt` automatically during Phase 0 — do not list them here. |
|
||||
| **3 Names** | Translate `names.json` (item/skill/enemy/map value names). WolfDawn tags each name with a per-entry **safety** badge (`safe`, `refs`, or `verify`). Phase 0 translates only `safe` entries; `refs` and `verify` names stay Japanese so inject skips them. Review the category breakdown for this game, pick **Translation mode** (Normal or Batch), and run **Translate safe names (Phase 0)**. |
|
||||
| **4 Database** | Review the **discovery summary** to see where this game's text lives (standard RPG sheets vs custom dialogue tables). Database sheets are classified as foundation (items, skills, descriptions — translate first) or narrative (custom event/profile sheets — translate after foundation). Use **Translate foundation DB** then **Translate narrative DB**, or tick specific sheets and run **Translate checked sheets only**. Optional: copy the **DB structure prompt** for an AI audit and import the returned JSON into `wolf_json/db_profile.json`. |
|
||||
|
|
|
|||
3
gameupdate/.gitignore
vendored
3
gameupdate/.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 GitLab’s 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
gameupdate/UberWolfCli.LICENSE.txt
Normal file
21
gameupdate/UberWolfCli.LICENSE.txt
Normal 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
gameupdate/UberWolfCli.exe
Normal file
BIN
gameupdate/UberWolfCli.exe
Normal file
Binary file not shown.
28
gameupdate/gameupdate/patch-config.example.txt
Normal file
28
gameupdate/gameupdate/patch-config.example.txt
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -585,6 +585,62 @@ class ConfigTab(QWidget):
|
|||
ui_form.addRow(font_scale_label, self.font_scale_spin)
|
||||
|
||||
right_column.addLayout(ui_form)
|
||||
|
||||
# Game Update defaults (written into gameupdate/patch-config.txt on copy)
|
||||
right_column.addWidget(create_section_header("📦 Game Update Defaults"))
|
||||
gu_form = QFormLayout()
|
||||
gu_form.setSpacing(6)
|
||||
gu_form.setContentsMargins(0, 0, 0, 12)
|
||||
gu_form.setFieldGrowthPolicy(QFormLayout.FieldsStayAtSizeHint)
|
||||
gu_form.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
|
||||
def _gu_label(text: str) -> QLabel:
|
||||
lbl = QLabel(text)
|
||||
lbl.setFixedWidth(150)
|
||||
lbl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
return lbl
|
||||
|
||||
self.gu_forge_combo = QComboBox()
|
||||
self.gu_forge_combo.addItem("GitLab / gitgud", "gitlab")
|
||||
self.gu_forge_combo.addItem("GitHub", "github")
|
||||
self.gu_forge_combo.addItem("Forgejo / Gitea", "forgejo")
|
||||
self.gu_forge_combo.setFixedWidth(220)
|
||||
self.gu_forge_combo.setToolTip(
|
||||
"Where your patch repos live. Used when Step 1 writes patch-config.txt."
|
||||
)
|
||||
self.gu_forge_combo.currentIndexChanged.connect(self._on_gu_forge_changed)
|
||||
gu_form.addRow(_gu_label("Forge:"), self.gu_forge_combo)
|
||||
|
||||
self.gu_host_edit = QLineEdit()
|
||||
self.gu_host_edit.setFixedWidth(220)
|
||||
self.gu_host_edit.setPlaceholderText("gitgud.io")
|
||||
self.gu_host_edit.setToolTip(
|
||||
"Hostname only (no https://). Leave blank to use the forge default."
|
||||
)
|
||||
gu_form.addRow(_gu_label("Host:"), self.gu_host_edit)
|
||||
|
||||
self.gu_username_edit = QLineEdit()
|
||||
self.gu_username_edit.setFixedWidth(220)
|
||||
self.gu_username_edit.setPlaceholderText("your-org-or-user")
|
||||
self.gu_username_edit.setToolTip(
|
||||
"Owner / org / group for all patch repos. Set once; rarely changes."
|
||||
)
|
||||
gu_form.addRow(_gu_label("Org / username:"), self.gu_username_edit)
|
||||
|
||||
self.gu_branch_edit = QLineEdit()
|
||||
self.gu_branch_edit.setFixedWidth(220)
|
||||
self.gu_branch_edit.setPlaceholderText("main")
|
||||
self.gu_branch_edit.setToolTip("Default branch to track (usually main or master).")
|
||||
gu_form.addRow(_gu_label("Branch:"), self.gu_branch_edit)
|
||||
|
||||
gu_hint = QLabel(
|
||||
"Copied into each game's gameupdate/patch-config.txt when you run "
|
||||
"Copy gameupdate/ (repo= is still per-game)."
|
||||
)
|
||||
gu_hint.setWordWrap(True)
|
||||
gu_hint.setStyleSheet("color:#7a7a7a;font-size:11px;")
|
||||
right_column.addLayout(gu_form)
|
||||
right_column.addWidget(gu_hint)
|
||||
right_column.addStretch()
|
||||
|
||||
# Add columns to layout
|
||||
|
|
@ -725,7 +781,39 @@ class ConfigTab(QWidget):
|
|||
|
||||
# UI settings
|
||||
self.font_scale_spin.setValue(float(_get("font_scale", "1.0")))
|
||||
|
||||
|
||||
# Game Update defaults
|
||||
from util.gameupdate_config import (
|
||||
DEFAULT_BRANCH,
|
||||
DEFAULT_FORGE,
|
||||
default_host_for_forge,
|
||||
normalize_forge,
|
||||
)
|
||||
|
||||
forge = normalize_forge(_get("gameUpdateForge", DEFAULT_FORGE))
|
||||
forge_idx = self.gu_forge_combo.findData(forge)
|
||||
self.gu_forge_combo.setCurrentIndex(forge_idx if forge_idx >= 0 else 0)
|
||||
host = _get("gameUpdateHost", "") or default_host_for_forge(forge)
|
||||
self.gu_host_edit.setText(host)
|
||||
self.gu_username_edit.setText(_get("gameUpdateUsername", ""))
|
||||
self.gu_branch_edit.setText(_get("gameUpdateBranch", DEFAULT_BRANCH) or DEFAULT_BRANCH)
|
||||
|
||||
def _on_gu_forge_changed(self, _index: int = 0):
|
||||
"""Fill host with the forge default when the user switches forge."""
|
||||
from util.gameupdate_config import default_host_for_forge
|
||||
|
||||
forge = self.gu_forge_combo.currentData() or "gitlab"
|
||||
current = self.gu_host_edit.text().strip()
|
||||
defaults = {
|
||||
"gitgud.io",
|
||||
"gitlab.com",
|
||||
"github.com",
|
||||
"codeberg.org",
|
||||
"",
|
||||
}
|
||||
if current.lower() in defaults:
|
||||
self.gu_host_edit.setText(default_host_for_forge(str(forge)))
|
||||
|
||||
def connect_auto_save(self):
|
||||
"""Connect all widgets to auto-save on change."""
|
||||
# Text fields - use editingFinished to avoid saving on every keystroke
|
||||
|
|
@ -750,6 +838,10 @@ class ConfigTab(QWidget):
|
|||
self.input_cost_spin.editingFinished.connect(self.auto_save)
|
||||
self.output_cost_spin.editingFinished.connect(self.auto_save)
|
||||
self.font_scale_spin.editingFinished.connect(self.auto_save)
|
||||
self.gu_forge_combo.currentIndexChanged.connect(self.auto_save)
|
||||
self.gu_host_edit.editingFinished.connect(self.auto_save)
|
||||
self.gu_username_edit.editingFinished.connect(self.auto_save)
|
||||
self.gu_branch_edit.editingFinished.connect(self.auto_save)
|
||||
|
||||
def disconnect_auto_save(self):
|
||||
"""Disconnect all widgets from auto-save."""
|
||||
|
|
@ -770,6 +862,10 @@ class ConfigTab(QWidget):
|
|||
self.input_cost_spin.editingFinished.disconnect(self.auto_save)
|
||||
self.output_cost_spin.editingFinished.disconnect(self.auto_save)
|
||||
self.font_scale_spin.editingFinished.disconnect(self.auto_save)
|
||||
self.gu_forge_combo.currentIndexChanged.disconnect(self.auto_save)
|
||||
self.gu_host_edit.editingFinished.disconnect(self.auto_save)
|
||||
self.gu_username_edit.editingFinished.disconnect(self.auto_save)
|
||||
self.gu_branch_edit.editingFinished.disconnect(self.auto_save)
|
||||
except (TypeError, RuntimeError):
|
||||
pass
|
||||
|
||||
|
|
@ -808,6 +904,10 @@ class ConfigTab(QWidget):
|
|||
"input_cost": str(self.input_cost_spin.value()),
|
||||
"output_cost": str(self.output_cost_spin.value()),
|
||||
"font_scale": str(self.font_scale_spin.value()),
|
||||
"gameUpdateForge": str(self.gu_forge_combo.currentData() or "gitlab"),
|
||||
"gameUpdateHost": self.gu_host_edit.text().strip(),
|
||||
"gameUpdateUsername": self.gu_username_edit.text().strip(),
|
||||
"gameUpdateBranch": self.gu_branch_edit.text().strip() or "main",
|
||||
}
|
||||
|
||||
# Save to .env file and update os.environ so subprocesses inherit new values
|
||||
|
|
@ -867,6 +967,15 @@ class ConfigTab(QWidget):
|
|||
|
||||
# UI settings
|
||||
self.font_scale_spin.setValue(1.0)
|
||||
|
||||
# Game Update defaults
|
||||
from util.gameupdate_config import DEFAULT_BRANCH, DEFAULT_FORGE, DEFAULT_HOST
|
||||
|
||||
forge_idx = self.gu_forge_combo.findData(DEFAULT_FORGE)
|
||||
self.gu_forge_combo.setCurrentIndex(forge_idx if forge_idx >= 0 else 0)
|
||||
self.gu_host_edit.setText(DEFAULT_HOST)
|
||||
self.gu_username_edit.clear()
|
||||
self.gu_branch_edit.setText(DEFAULT_BRANCH)
|
||||
|
||||
self.connect_auto_save()
|
||||
|
||||
|
|
@ -902,6 +1011,10 @@ class ConfigTab(QWidget):
|
|||
"input_cost": self.input_cost_spin.value(),
|
||||
"output_cost": self.output_cost_spin.value(),
|
||||
"font_scale": self.font_scale_spin.value(),
|
||||
"gameUpdateForge": str(self.gu_forge_combo.currentData() or "gitlab"),
|
||||
"gameUpdateHost": self.gu_host_edit.text().strip(),
|
||||
"gameUpdateUsername": self.gu_username_edit.text().strip(),
|
||||
"gameUpdateBranch": self.gu_branch_edit.text().strip() or "main",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
|
|
|
|||
|
|
@ -1030,7 +1030,9 @@ class WolfWorkflowTab(QWidget):
|
|||
tc_inner.setSpacing(4)
|
||||
tc_desc = QLabel(
|
||||
"Copies everything from the gameupdate/ folder into the game's root folder, "
|
||||
"overwriting existing files (updater scripts, patch scripts, .gitignore)."
|
||||
"overwriting existing files (updater scripts, patch scripts, .gitignore, "
|
||||
"and UberWolfCli.exe). Writes patch-config.txt from Config → Game Update "
|
||||
"defaults (set your org once there; edit repo= per game)."
|
||||
)
|
||||
tc_desc.setWordWrap(True)
|
||||
tc_desc.setStyleSheet("color:#9d9d9d;font-size:13px;")
|
||||
|
|
@ -1456,6 +1458,16 @@ class WolfWorkflowTab(QWidget):
|
|||
|
||||
self._run_task(task)
|
||||
|
||||
def _write_gameupdate_patch_config(self, game_root: str):
|
||||
"""Write gameupdate/patch-config.txt from Config → Game Update defaults."""
|
||||
from util.gameupdate_config import write_patch_config
|
||||
|
||||
ok, msg = write_patch_config(game_root)
|
||||
if ok:
|
||||
self._log(f"📝 Wrote patch-config.txt from Config defaults → {msg}")
|
||||
else:
|
||||
self._log(f"ℹ patch-config.txt: {msg}")
|
||||
|
||||
def _run_gameupdate(self):
|
||||
src = self.pp_gameupdate_edit.text().strip()
|
||||
dst = self.folder_edit.text().strip()
|
||||
|
|
@ -1478,6 +1490,9 @@ class WolfWorkflowTab(QWidget):
|
|||
self._log(f"✅ gameupdate: copied {count} file(s).")
|
||||
for e in errors:
|
||||
self._log(f" ⚠ {e}")
|
||||
dst = self.folder_edit.text().strip()
|
||||
if dst and not errors:
|
||||
self._write_gameupdate_patch_config(dst)
|
||||
|
||||
def _run_all_preprocess(self):
|
||||
if not self._game_root:
|
||||
|
|
@ -1523,6 +1538,13 @@ class WolfWorkflowTab(QWidget):
|
|||
except Exception as exc:
|
||||
errors.append(f"{rel}: {exc}")
|
||||
log(f" Copied {copied} gameupdate file(s).")
|
||||
from util.gameupdate_config import write_patch_config
|
||||
|
||||
ok, msg = write_patch_config(dst)
|
||||
if ok:
|
||||
log(f" Wrote patch-config.txt from Config defaults → {msg}")
|
||||
else:
|
||||
log(f" patch-config.txt: {msg}")
|
||||
if errors:
|
||||
return False, "Pre-process finished with errors (see log)."
|
||||
return True, "Pre-process: dazedformat and gameupdate copy finished."
|
||||
|
|
|
|||
|
|
@ -1716,7 +1716,9 @@ class WorkflowTab(QWidget):
|
|||
tc_inner.setSpacing(4)
|
||||
tc_desc = QLabel(
|
||||
"Copies everything from the <code>gameupdate/</code> folder "
|
||||
"into the game\'s root folder, overwriting existing files."
|
||||
"into the game\'s root folder, overwriting existing files. "
|
||||
"Writes <code>patch-config.txt</code> from Config → Game Update defaults "
|
||||
"(set your org once there; edit <code>repo=</code> per game)."
|
||||
)
|
||||
tc_desc.setTextFormat(Qt.RichText)
|
||||
tc_desc.setWordWrap(True)
|
||||
|
|
@ -4612,6 +4614,16 @@ class WorkflowTab(QWidget):
|
|||
self._worker = w
|
||||
w.start()
|
||||
|
||||
def _write_gameupdate_patch_config(self, game_root: str):
|
||||
"""Write gameupdate/patch-config.txt from Config → Game Update defaults."""
|
||||
from util.gameupdate_config import write_patch_config
|
||||
|
||||
ok, msg = write_patch_config(game_root)
|
||||
if ok:
|
||||
self._log(f"📝 Wrote patch-config.txt from Config defaults → {msg}")
|
||||
else:
|
||||
self._log(f"ℹ patch-config.txt: {msg}")
|
||||
|
||||
def _run_gameupdate(self):
|
||||
src = self.pp_gameupdate_edit.text().strip()
|
||||
dst = self.folder_edit.text().strip()
|
||||
|
|
@ -4634,6 +4646,9 @@ class WorkflowTab(QWidget):
|
|||
self._log(f"✅ gameupdate: copied {count} file(s).")
|
||||
for e in errors:
|
||||
self._log(f" ⚠ {e}")
|
||||
dst = self.folder_edit.text().strip()
|
||||
if dst and not errors:
|
||||
self._write_gameupdate_patch_config(dst)
|
||||
|
||||
def _run_all_preprocess(self):
|
||||
"""Launch all three pre-process tasks in sequence, chaining via signals."""
|
||||
|
|
@ -4688,6 +4703,8 @@ class WorkflowTab(QWidget):
|
|||
self._log(f"✅ gameupdate: copied {count} file(s).")
|
||||
for e in errors:
|
||||
self._log(f" ⚠ {e}")
|
||||
if not errors and game_root_dst:
|
||||
self._write_gameupdate_patch_config(game_root_dst)
|
||||
run_next(rest)
|
||||
worker.done.connect(on_copy_done)
|
||||
else:
|
||||
|
|
|
|||
132
util/gameupdate_config.py
Normal file
132
util/gameupdate_config.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"""GameUpdate patch-config defaults from the tool's .env / Config tab.
|
||||
|
||||
Translators set forge/host/org once in Config. When Step 1 copies
|
||||
``gameupdate/`` into a game root, we write ``gameupdate/patch-config.txt``
|
||||
from those defaults (repo stays per-game).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import dotenv_values
|
||||
|
||||
# .env keys (Config tab)
|
||||
ENV_FORGE = "gameUpdateForge"
|
||||
ENV_HOST = "gameUpdateHost"
|
||||
ENV_USERNAME = "gameUpdateUsername"
|
||||
ENV_BRANCH = "gameUpdateBranch"
|
||||
|
||||
DEFAULT_FORGE = "gitlab"
|
||||
DEFAULT_HOST = "gitgud.io"
|
||||
DEFAULT_BRANCH = "main"
|
||||
|
||||
_FORGE_HOST_DEFAULTS = {
|
||||
"gitlab": "gitgud.io",
|
||||
"github": "github.com",
|
||||
"forgejo": "codeberg.org",
|
||||
}
|
||||
|
||||
|
||||
def normalize_forge(raw: str | None) -> str:
|
||||
value = (raw or "").strip().lower()
|
||||
if value in ("", "gitlab", "gl", "gitgud"):
|
||||
return "gitlab"
|
||||
if value in ("github", "gh"):
|
||||
return "github"
|
||||
if value in ("forgejo", "gitea", "fj", "codeberg"):
|
||||
return "forgejo"
|
||||
return "gitlab"
|
||||
|
||||
|
||||
def default_host_for_forge(forge: str) -> str:
|
||||
return _FORGE_HOST_DEFAULTS.get(normalize_forge(forge), DEFAULT_HOST)
|
||||
|
||||
|
||||
def load_gameupdate_defaults(env_path: str | Path | None = None) -> dict[str, str]:
|
||||
"""Return forge/host/username/branch from .env (with process env fallback)."""
|
||||
path = Path(env_path) if env_path is not None else Path(".env")
|
||||
file_vals = dotenv_values(path) if path.is_file() else {}
|
||||
|
||||
def _get(key: str, default: str = "") -> str:
|
||||
raw = file_vals.get(key)
|
||||
if raw is None or str(raw).strip() == "":
|
||||
raw = os.getenv(key, default)
|
||||
return str(raw or default).strip()
|
||||
|
||||
forge = normalize_forge(_get(ENV_FORGE, DEFAULT_FORGE))
|
||||
host = _get(ENV_HOST, "") or default_host_for_forge(forge)
|
||||
host = host.replace("https://", "").replace("http://", "").split("/")[0].strip()
|
||||
return {
|
||||
"forge": forge,
|
||||
"host": host or default_host_for_forge(forge),
|
||||
"username": _get(ENV_USERNAME, ""),
|
||||
"branch": _get(ENV_BRANCH, DEFAULT_BRANCH) or DEFAULT_BRANCH,
|
||||
}
|
||||
|
||||
|
||||
def format_patch_config(
|
||||
*,
|
||||
forge: str,
|
||||
host: str,
|
||||
username: str,
|
||||
repo: str = "YOUR_PATCH_REPO",
|
||||
branch: str = DEFAULT_BRANCH,
|
||||
) -> str:
|
||||
"""Render a patch-config.txt body."""
|
||||
forge = normalize_forge(forge)
|
||||
host = (host or default_host_for_forge(forge)).strip()
|
||||
username = (username or "").strip() or "YOUR_ORG_OR_USER"
|
||||
repo = (repo or "").strip() or "YOUR_PATCH_REPO"
|
||||
branch = (branch or "").strip() or DEFAULT_BRANCH
|
||||
|
||||
lines = [
|
||||
"# Generated by DazedMTLTool from Config → Game Update defaults.",
|
||||
"# Edit repo= per game. forge/host/username usually stay the same.",
|
||||
f"forge={forge}",
|
||||
f"host={host}",
|
||||
f"username={username}",
|
||||
f"repo={repo}",
|
||||
f"branch={branch}",
|
||||
"",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def write_patch_config(
|
||||
game_root: str | Path,
|
||||
*,
|
||||
repo: str | None = None,
|
||||
env_path: str | Path | None = None,
|
||||
overwrite: bool = True,
|
||||
) -> tuple[bool, str]:
|
||||
"""Write ``<game_root>/gameupdate/patch-config.txt`` from Config defaults.
|
||||
|
||||
Returns ``(ok, message)``.
|
||||
"""
|
||||
root = Path(game_root)
|
||||
dest_dir = root / "gameupdate"
|
||||
dest = dest_dir / "patch-config.txt"
|
||||
if dest.is_file() and not overwrite:
|
||||
return False, f"left existing {dest}"
|
||||
|
||||
defaults = load_gameupdate_defaults(env_path)
|
||||
if not defaults["username"]:
|
||||
return False, (
|
||||
"skipped patch-config.txt (set Game Update org/username in Config first)"
|
||||
)
|
||||
|
||||
body = format_patch_config(
|
||||
forge=defaults["forge"],
|
||||
host=defaults["host"],
|
||||
username=defaults["username"],
|
||||
repo=repo or "YOUR_PATCH_REPO",
|
||||
branch=defaults["branch"],
|
||||
)
|
||||
try:
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_text(body, encoding="utf-8")
|
||||
except Exception as exc:
|
||||
return False, f"could not write {dest}: {exc}"
|
||||
return True, str(dest)
|
||||
Loading…
Reference in a new issue