274 lines
7.6 KiB
PowerShell
274 lines
7.6 KiB
PowerShell
param(
|
||
[Parameter(Position = 0, Mandatory = $true)]
|
||
[ValidateSet("unpack", "pack")]
|
||
[string]$Command
|
||
)
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
$Root = $PSScriptRoot
|
||
$PacDir = Join-Path $Root "pac"
|
||
$Archive = Join-Path $PacDir "ysbin.ypf"
|
||
$ExtractedRoot = Join-Path $PacDir "ysbin"
|
||
$ExtractedScripts = Join-Path $ExtractedRoot "ysbin"
|
||
$YpfExe = Join-Path $Root "ypf\YPF.exe"
|
||
|
||
$ToolsDir = Join-Path $Root "VNTranslationTools"
|
||
$VNTextPatchExe = Join-Path $ToolsDir "VNTextPatch.exe"
|
||
$ToolScripts = Join-Path $ToolsDir "ysbin"
|
||
|
||
$OutputDir = Join-Path $Root "output"
|
||
$ResultDir = Join-Path $Root "res"
|
||
$BackupArchive = Join-Path $PacDir "ysbin.ypf.bak"
|
||
|
||
function Require-File {
|
||
param([string]$Path)
|
||
|
||
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
||
throw "Required file not found: $Path"
|
||
}
|
||
}
|
||
|
||
function Require-Dir {
|
||
param([string]$Path)
|
||
|
||
if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
|
||
throw "Required folder not found: $Path"
|
||
}
|
||
}
|
||
|
||
function Reset-Dir {
|
||
param([string]$Path)
|
||
|
||
if (Test-Path -LiteralPath $Path) {
|
||
Remove-Item -LiteralPath $Path -Recurse -Force
|
||
}
|
||
|
||
New-Item -ItemType Directory -Path $Path | Out-Null
|
||
}
|
||
|
||
function Copy-DirectoryContents {
|
||
param(
|
||
[string]$Source,
|
||
[string]$Destination
|
||
)
|
||
|
||
Require-Dir $Source
|
||
Reset-Dir $Destination
|
||
Get-ChildItem -LiteralPath $Source -Force | Copy-Item -Destination $Destination -Recurse -Force
|
||
}
|
||
|
||
function Resolve-ExePath {
|
||
param([Parameter(Mandatory)][string]$Path)
|
||
|
||
try {
|
||
(Resolve-Path -LiteralPath $Path).Path
|
||
}
|
||
catch {
|
||
throw $_.Exception
|
||
}
|
||
}
|
||
|
||
function Set-RunAsInvokerCompatLayer {
|
||
param([Parameter(Mandatory)][string]$ExeFullPath)
|
||
|
||
if ([string]::IsNullOrWhiteSpace($ExeFullPath) -or -not (Test-Path -LiteralPath $ExeFullPath -PathType Leaf)) {
|
||
return
|
||
}
|
||
|
||
# Many fan tools ship with requireAdministrator for no real reason. RUNASINVOKER suppresses the UAC prompt.
|
||
$layersKey = 'HKCU:\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers'
|
||
if (-not (Test-Path -LiteralPath $layersKey)) {
|
||
New-Item -Path $layersKey -Force | Out-Null
|
||
}
|
||
|
||
try {
|
||
Set-ItemProperty -LiteralPath $layersKey -Name $ExeFullPath -Value 'RUNASINVOKER' -Type String -Force
|
||
}
|
||
catch {
|
||
# Non-Windows hosts, locked-down profiles, etc.
|
||
}
|
||
}
|
||
|
||
function Run-Tool {
|
||
param(
|
||
[string]$WorkingDirectory,
|
||
[string]$Executable,
|
||
[string[]]$Arguments
|
||
)
|
||
|
||
if (Test-Path -LiteralPath $Executable) {
|
||
try {
|
||
Unblock-File -LiteralPath $Executable -ErrorAction Stop | Out-Null
|
||
}
|
||
catch {
|
||
}
|
||
}
|
||
|
||
$wdPath = Resolve-ExePath $WorkingDirectory
|
||
$exePath = Resolve-ExePath $Executable
|
||
Set-RunAsInvokerCompatLayer -ExeFullPath $exePath
|
||
|
||
function Expand-BlockedHint {
|
||
param([Parameter(Mandatory)][string]$Path)
|
||
@"
|
||
|
||
Could not launch the executable (SmartScreen / antivirus blocked the child .exe).
|
||
|
||
Try:
|
||
1. Properties → Unblock, or: Unblock-File -LiteralPath "$Path"
|
||
2. SmartScreen: More info → Run anyway
|
||
3. If you use Cursor’s terminal: open Windows Terminal, cd here, run again (sandboxed shells block some tools).
|
||
|
||
"@
|
||
|
||
}
|
||
|
||
function Interpret-DirectNativeExitCode {
|
||
# Calling tools with & frequently leaves LASTEXITCODE empty even on success — don't treat blank as failure.
|
||
$gv = Get-Variable -Name LASTEXITCODE -ErrorAction SilentlyContinue
|
||
if ($null -eq $gv) {
|
||
return 0
|
||
}
|
||
|
||
$tokRaw = $gv.Value
|
||
$tok = if ($null -eq $tokRaw) { '' } else { "$tokRaw".Trim() }
|
||
|
||
if ([string]::IsNullOrWhiteSpace($tok)) {
|
||
return 0
|
||
}
|
||
|
||
$parsed = 0
|
||
if (-not [int]::TryParse($tok, [ref]$parsed)) {
|
||
Write-Warning "ysbin-patch: non-integer LASTEXITCODE '$tok' — assuming success."
|
||
return 0
|
||
}
|
||
|
||
return $parsed
|
||
}
|
||
|
||
Push-Location $wdPath
|
||
try {
|
||
try {
|
||
& $exePath @Arguments
|
||
}
|
||
catch {
|
||
$raw = $_.Exception.Message
|
||
if ($raw -match 'canceled by the user|OPERATION_ABORTED|The operation has been aborted|800704C7') {
|
||
$blockMsg = (Expand-BlockedHint -Path $exePath).TrimEnd()
|
||
throw "${blockMsg}`n`nInner: $raw"
|
||
}
|
||
|
||
throw $_
|
||
}
|
||
|
||
$exitCodeDirect = Interpret-DirectNativeExitCode
|
||
if ($exitCodeDirect -ne 0) {
|
||
throw "Command failed with exit code ${exitCodeDirect}: $exePath $($Arguments -join ' ')"
|
||
}
|
||
}
|
||
finally {
|
||
Pop-Location
|
||
}
|
||
}
|
||
|
||
function Restore-VanillaArchive {
|
||
Require-File $BackupArchive
|
||
Copy-Item -LiteralPath $BackupArchive -Destination $Archive -Force
|
||
Write-Host "Copied ysbin.ypf.bak -> pac\ysbin.ypf"
|
||
}
|
||
|
||
function Apply-SpacingFix {
|
||
$fix = Join-Path $PacDir "yst00042.ybn"
|
||
if (-not (Test-Path -LiteralPath $fix)) {
|
||
return
|
||
}
|
||
Require-Dir $ExtractedScripts
|
||
Copy-Item -LiteralPath $fix -Destination (Join-Path $ExtractedScripts "yst00042.ybn") -Force
|
||
Write-Host "Applied pac\yst00042.ybn (spacing fix)"
|
||
}
|
||
|
||
function Unpack-Archive {
|
||
Require-File $YpfExe
|
||
Require-File $Archive
|
||
|
||
Write-Host "Unpacking pac\ysbin.ypf..."
|
||
if (Test-Path -LiteralPath $ExtractedRoot) {
|
||
Remove-Item -LiteralPath $ExtractedRoot -Recurse -Force
|
||
}
|
||
|
||
Run-Tool -WorkingDirectory $PacDir -Executable $YpfExe -Arguments @("-e", "ysbin.ypf")
|
||
Require-Dir $ExtractedScripts
|
||
}
|
||
|
||
function Extract-Json {
|
||
Require-File $VNTextPatchExe
|
||
|
||
Copy-DirectoryContents -Source $ExtractedScripts -Destination $ToolScripts
|
||
|
||
if (-not (Test-Path -LiteralPath $OutputDir)) {
|
||
New-Item -ItemType Directory -Path $OutputDir | Out-Null
|
||
}
|
||
|
||
Write-Host "Extracting JSON to output..."
|
||
Run-Tool -WorkingDirectory $ToolsDir -Executable $VNTextPatchExe -Arguments @("extractlocal", "ysbin", $OutputDir)
|
||
}
|
||
|
||
function Insert-Json {
|
||
Require-File $VNTextPatchExe
|
||
Require-Dir $ToolScripts
|
||
Require-Dir $OutputDir
|
||
|
||
Reset-Dir $ResultDir
|
||
|
||
Write-Host "Inserting JSON into scripts..."
|
||
Run-Tool -WorkingDirectory $ToolsDir -Executable $VNTextPatchExe -Arguments @("insertlocal", "ysbin", $OutputDir, $ResultDir)
|
||
}
|
||
|
||
function Merge-ResultIntoExtractedScripts {
|
||
Require-Dir $ResultDir
|
||
|
||
Write-Host "Merging patched files from res into pac\ysbin\ysbin..."
|
||
Get-ChildItem -LiteralPath $ResultDir -Force | Copy-Item -Destination $ExtractedScripts -Recurse -Force
|
||
}
|
||
|
||
function Pack-Ypf {
|
||
Require-File $YpfExe
|
||
Require-Dir $ExtractedScripts
|
||
|
||
if ((Test-Path -LiteralPath $Archive) -and -not (Test-Path -LiteralPath $BackupArchive)) {
|
||
Copy-Item -LiteralPath $Archive -Destination $BackupArchive
|
||
Write-Host "Backed up original archive to pac\ysbin.ypf.bak"
|
||
}
|
||
|
||
Write-Host "Packing pac\ysbin into pac\ysbin.ypf..."
|
||
Run-Tool -WorkingDirectory $PacDir -Executable $YpfExe -Arguments @("-c", "ysbin", "-v", "500")
|
||
}
|
||
|
||
function Invoke-PackPipeline {
|
||
Require-File $VNTextPatchExe
|
||
Require-Dir $OutputDir
|
||
|
||
Restore-VanillaArchive
|
||
Unpack-Archive
|
||
Apply-SpacingFix
|
||
|
||
Copy-DirectoryContents -Source $ExtractedScripts -Destination $ToolScripts
|
||
Insert-Json
|
||
Merge-ResultIntoExtractedScripts
|
||
Pack-Ypf
|
||
}
|
||
|
||
switch ($Command) {
|
||
"unpack" {
|
||
Restore-VanillaArchive
|
||
Unpack-Archive
|
||
Apply-SpacingFix
|
||
Extract-Json
|
||
Write-Host "Done. pac\ysbin.ypf -> pac\ysbin\ ; strings -> output\ (*.json)."
|
||
}
|
||
"pack" {
|
||
Invoke-PackPipeline
|
||
Write-Host "Done. output\ JSON -> scripts/ybn in pac\ysbin\ ; -> pac\ysbin.ypf."
|
||
}
|
||
}
|