Restore native inference, card-bound readings and paged dialogue. Provide manual, checksum-verified model setup instead of Git LFS so source checkouts work without uploading multi-gigabyte weights. The game stays offline; complete release archives still bundle the model.
88 lines
4.2 KiB
PowerShell
88 lines
4.2 KiB
PowerShell
param(
|
|
[string]$ManifestPath = '',
|
|
[string]$AiDirectory = ''
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
$root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
|
if (!$ManifestPath) { $ManifestPath = Join-Path $root 'ai/model.json' }
|
|
if (!$AiDirectory) { $AiDirectory = Join-Path $root 'ai' }
|
|
$lock = $null
|
|
|
|
function Test-Model([string]$Path, $Manifest) {
|
|
if (!(Test-Path -LiteralPath $Path -PathType Leaf)) { return $false }
|
|
if ((Get-Item -LiteralPath $Path).Length -ne [long]$Manifest.bytes) { return $false }
|
|
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash -eq $Manifest.sha256
|
|
}
|
|
|
|
try {
|
|
$manifest = Get-Content -LiteralPath $ManifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
if ([long]$manifest.bytes -le 0 -or $manifest.sha256 -notmatch '^[a-fA-F0-9]{64}$') {
|
|
throw 'Invalid model size or SHA-256 in manifest'
|
|
}
|
|
$ai = [IO.Path]::GetFullPath($AiDirectory).TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar
|
|
$model = [IO.Path]::GetFullPath((Join-Path $ai $manifest.file))
|
|
if (!$model.StartsWith($ai, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw 'Model destination must be inside the ai directory'
|
|
}
|
|
if (Test-Model $model $manifest) {
|
|
Write-Host 'SUCCESS: model already present; size and SHA-256 verified. Nothing to download.'
|
|
exit 0
|
|
}
|
|
$curl = (Get-Command curl.exe -ErrorAction Stop).Source
|
|
New-Item -ItemType Directory -Force -Path ([IO.Path]::GetDirectoryName($model)) | Out-Null
|
|
$lock = [IO.File]::Open("$model.download.lock", [IO.FileMode]::OpenOrCreate,
|
|
[IO.FileAccess]::ReadWrite, [IO.FileShare]::None)
|
|
$part = "$model.part"
|
|
$urls = @($manifest.download_urls)
|
|
if (!$urls.Count -or !$urls[0]) { throw 'No download URLs in model manifest' }
|
|
Write-Host ('Download size: {0:N2} GB. Keep at least 3 GB free.' -f ([long]$manifest.bytes / 1e9))
|
|
Write-Host "Destination: $model"
|
|
foreach ($url in $urls) {
|
|
$uri = [Uri]$url
|
|
if ($uri.Scheme -ne 'https' -and !($uri.Scheme -eq 'http' -and $uri.IsLoopback)) {
|
|
throw 'Model downloads require HTTPS'
|
|
}
|
|
Write-Host "Source: $($uri.Host)"
|
|
if (Test-Path -LiteralPath $part) {
|
|
$length = (Get-Item -LiteralPath $part).Length
|
|
if ($length -eq [long]$manifest.bytes -and (Test-Model $part $manifest)) {
|
|
Move-Item -LiteralPath $part -Destination $model -Force
|
|
Write-Host 'SUCCESS: completed partial file verified and installed.'
|
|
exit 0
|
|
}
|
|
if ($length -ge [long]$manifest.bytes) { Remove-Item -LiteralPath $part }
|
|
}
|
|
# curl validates range responses before appending. Keep partial data on outages.
|
|
$arguments = @('--fail', '--location', '--proto', '=http,https', '--proto-redir', '=http,https',
|
|
'--retry', '2', '--retry-delay', '1', '--connect-timeout', '30',
|
|
'--speed-limit', '1024', '--speed-time', '60', '--continue-at', '-', '--output', $part, $url)
|
|
& $curl @arguments
|
|
$result = $LASTEXITCODE
|
|
if ($result -eq 33 -or $result -eq 36) {
|
|
# Some mirrors ignore Range; retry this source once from the beginning.
|
|
if (Test-Path -LiteralPath $part) { Remove-Item -LiteralPath $part }
|
|
& $curl @arguments
|
|
$result = $LASTEXITCODE
|
|
}
|
|
if ($result -ne 0) {
|
|
Write-Warning "Download interrupted (curl $result). Trying next source; partial data retained."
|
|
continue
|
|
}
|
|
Write-Host 'Verifying downloaded size and SHA-256...'
|
|
if (!(Test-Model $part $manifest)) {
|
|
Write-Warning 'Downloaded file failed integrity verification. Trying next source.'
|
|
if (Test-Path -LiteralPath $part) { Remove-Item -LiteralPath $part }
|
|
continue
|
|
}
|
|
# Never replace an existing destination with unverified data.
|
|
Move-Item -LiteralPath $part -Destination $model -Force
|
|
Write-Host 'SUCCESS: offline model downloaded, verified and installed.'
|
|
exit 0
|
|
}
|
|
throw 'All model sources failed. Run download_fortune_model.bat again to retry/resume.'
|
|
} catch {
|
|
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
|
|
exit 1
|
|
} finally {
|
|
if ($lock) { $lock.Dispose() }
|
|
}
|