mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
Linux only: the PowerShell scripts from the boilerplate go
The eight tools/*.ps1 scripts came in with 4df5c68 from
ps5-native-app-boilerplate and nothing in the build, the gates or the
docs runs them: the project builds and deploys from Linux only.
tools/prepare-assets.sh only wrapped prepare-assets.ps1, so it goes
too. .gitattributes loses the CRLF rule for *.ps1 and tools/lint.sh
no longer lists .ps1 among the files whose headers it checks.
Evidence: git grep finds no reference to any removed script;
tools/verify.sh format PASS; tools/verify.sh unit PASS (283 s,
klog/unit-gate.log).
This commit is contained in:
1 parent
247336399d
commit
8f14452e5d
11 files changed
+1
-1108
No files matched your search
@@ -14,7 +14,6 @@ Makefile text eol=lf
|
||||
.clang-format text eol=lf
|
||||
.clang-tidy text eol=lf
|
||||
tooling/prospero-clang18 text eol=lf
|
||||
*.ps1 text eol=crlf
|
||||
*.png binary
|
||||
*.dds binary
|
||||
*.at9 binary
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<#
|
||||
ps5-native-app-boilerplate - Source attribution header audit.
|
||||
Copyright (C) 2026 BlackBearReloaded
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
Verifies attribution in every tracked, comment-capable code and tooling file.
|
||||
#>
|
||||
|
||||
#requires -Version 5.1
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
$patterns = @("*.c", "*.cc", "*.cpp", "*.h", "*.hpp", "*.ld", "*.py", "*.ps1", "*.yml", "*.yaml", "*.sh")
|
||||
$files = @(& git -C $root ls-files --cached --others --exclude-standard -- @patterns |
|
||||
Where-Object { Test-Path -LiteralPath (Join-Path $root $_) -PathType Leaf })
|
||||
$files += @(
|
||||
".editorconfig",
|
||||
".clang-format",
|
||||
".clang-tidy",
|
||||
".gitattributes",
|
||||
".gitignore",
|
||||
".env.example",
|
||||
"Makefile",
|
||||
"tooling/prospero-clang18",
|
||||
"tooling/native/runtime/api-surface.txt",
|
||||
"tooling/native/runtime/imports.txt"
|
||||
)
|
||||
$files = @($files | Sort-Object -Unique)
|
||||
$markers = @(
|
||||
"ps5-native-app-boilerplate",
|
||||
"Copyright (C) 2026 BlackBearReloaded",
|
||||
"SPDX-License-Identifier: GPL-3.0-or-later"
|
||||
)
|
||||
$missing = @()
|
||||
|
||||
foreach ($file in $files) {
|
||||
$path = Join-Path $root $file
|
||||
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
|
||||
$missing += "$file (missing file)"
|
||||
continue
|
||||
}
|
||||
|
||||
$header = (Get-Content -LiteralPath $path -TotalCount 20) -join "`n"
|
||||
foreach ($marker in $markers) {
|
||||
if (-not $header.Contains($marker)) {
|
||||
$missing += "$file (missing '$marker')"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($missing.Count -ne 0) {
|
||||
$missing | ForEach-Object { Write-Error $_ }
|
||||
throw "One or more source attribution headers are incomplete."
|
||||
}
|
||||
|
||||
Write-Host "Verified attribution headers in $($files.Count) tracked code and tooling files."
|
||||
@@ -1,34 +0,0 @@
|
||||
<#
|
||||
ps5-native-app-boilerplate - Windows prerequisite-check frontend.
|
||||
Copyright (C) 2026 BlackBearReloaded
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
Runs the canonical read-only Linux/WSL doctor so both host entry points report
|
||||
the same required and optional tools.
|
||||
#>
|
||||
|
||||
#requires -Version 5.1
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Fail([string]$Message) {
|
||||
throw "ps5-native-app-boilerplate: $Message"
|
||||
}
|
||||
|
||||
function Convert-ToWslPath([string]$Path) {
|
||||
$absolute = [IO.Path]::GetFullPath($Path)
|
||||
if ($absolute -notmatch '^([A-Za-z]):\\(.*)$') {
|
||||
Fail "The repository must be on a Windows drive visible to WSL."
|
||||
}
|
||||
return "/mnt/$($Matches[1].ToLowerInvariant())/$($Matches[2].Replace('\', '/'))"
|
||||
}
|
||||
|
||||
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) {
|
||||
Fail "WSL was not found. Install WSL and a Linux distribution first."
|
||||
}
|
||||
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
$doctor = Convert-ToWslPath (Join-Path $root "tools/doctor.sh")
|
||||
& wsl.exe --exec bash $doctor
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "One or more Linux/WSL prerequisites are missing."
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
<#
|
||||
ps5-native-app-boilerplate - Static ELF/FSELF validator.
|
||||
Copyright (C) 2026 BlackBearReloaded
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
Checks the loader-visible structure of a generated native application.
|
||||
#>
|
||||
|
||||
#requires -Version 5.1
|
||||
param(
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
[string]$File
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$path = (Resolve-Path -LiteralPath $File).Path
|
||||
$data = [IO.File]::ReadAllBytes($path)
|
||||
|
||||
function U16([int]$offset) { [BitConverter]::ToUInt16($data, $offset) }
|
||||
function U32([int]$offset) { [BitConverter]::ToUInt32($data, $offset) }
|
||||
function U64([int]$offset) { [BitConverter]::ToUInt64($data, $offset) }
|
||||
function Hex([uint64]$value) { "0x{0:x}" -f $value }
|
||||
function Align16([uint64]$value) { ($value + 15) -band ([uint64]::MaxValue - 15) }
|
||||
|
||||
$container = $false
|
||||
$elf = 0
|
||||
$fselfCompatible = $null
|
||||
$authInfoEmbedded = $null
|
||||
|
||||
if ($data.Length -ge 0x20 -and (U32 0) -in @([uint32]0x1D3D154F, [uint32]4009038932)) {
|
||||
$container = $true
|
||||
$entries = U16 0x18
|
||||
$headerSize = U16 0x0c
|
||||
$metaSize = U16 0x0e
|
||||
$elf = 0x20 + 0x20 * $entries
|
||||
if ($elf + 0x40 -gt $data.Length -or (U32 $elf) -ne 0x464c457f) {
|
||||
throw "FSELF does not contain an ELF header at the segment-table boundary."
|
||||
}
|
||||
|
||||
$phoff = U64 ($elf + 0x20)
|
||||
$phnum = U16 ($elf + 0x38)
|
||||
$ext = Align16 ($elf + $phoff + 0x38 * $phnum)
|
||||
$extMarker = if ($ext + 16 -le $data.Length) { U64 ([int]$ext + 8) } else { 0 }
|
||||
$fselfCompatible = $extMarker -eq 1
|
||||
|
||||
# This is where current kstuff-lite looks for an optional 0x88-byte auth record.
|
||||
$authOffset = $ext + 0x40 + 0x30 + 0x50 * $entries + 0x50
|
||||
$authInfoEmbedded = $authOffset + 8 -le $data.Length -and (U64 ([int]$authOffset)) -eq 0x88
|
||||
|
||||
[pscustomobject]@{
|
||||
Layer = "FSELF"
|
||||
SegmentEntries = $entries
|
||||
HeaderSize = Hex $headerSize
|
||||
MetaSize = Hex $metaSize
|
||||
ElfOffset = Hex $elf
|
||||
ExtendedInfoOffset = Hex $ext
|
||||
ExtendedInfoMarker = Hex $extMarker
|
||||
KstuffRecognized = $fselfCompatible
|
||||
EmbeddedAuthInfo = $authInfoEmbedded
|
||||
} | Format-List
|
||||
}
|
||||
|
||||
if ($elf + 0x40 -gt $data.Length -or (U32 $elf) -ne 0x464c457f) {
|
||||
throw "Input is neither a raw ELF nor a supported FSELF."
|
||||
}
|
||||
|
||||
$osAbi = $data[$elf + 7]
|
||||
$abiVersion = $data[$elf + 8]
|
||||
$type = U16 ($elf + 0x10)
|
||||
$entry = U64 ($elf + 0x18)
|
||||
$phoff = U64 ($elf + 0x20)
|
||||
$phentsize = U16 ($elf + 0x36)
|
||||
$phnum = U16 ($elf + 0x38)
|
||||
|
||||
$types = @()
|
||||
$mappedLoads = @()
|
||||
$procParam = $null
|
||||
$linkingLoad = $null
|
||||
$programHeaders = @()
|
||||
for ($i = 0; $i -lt $phnum; $i++) {
|
||||
$p = $elf + [int]$phoff + $i * $phentsize
|
||||
$pt = U32 $p
|
||||
$flags = U32 ($p + 4)
|
||||
$offset = U64 ($p + 8)
|
||||
$vaddr = U64 ($p + 16)
|
||||
$filesz = U64 ($p + 32)
|
||||
$memsz = U64 ($p + 40)
|
||||
$align = U64 ($p + 48)
|
||||
$programHeaders += [pscustomobject]@{
|
||||
Index=$i; Type=$pt; Flags=$flags; Offset=$offset; Address=$vaddr
|
||||
FileSize=$filesz; MemorySize=$memsz; Align=$align
|
||||
}
|
||||
$types += $pt
|
||||
if ($pt -eq 1) {
|
||||
if ($flags -eq 0) { $linkingLoad = $i }
|
||||
else { $mappedLoads += [pscustomobject]@{ Index=$i; Flags=$flags; Offset=$offset; Address=$vaddr; FileSize=$filesz; MemorySize=$memsz; Align=$align } }
|
||||
}
|
||||
if ($pt -eq 0x61000001) { $procParam = $offset }
|
||||
}
|
||||
|
||||
$errors = [Collections.Generic.List[string]]::new()
|
||||
if ($osAbi -ne 9) { $errors.Add("OS/ABI is not FreeBSD (9).") }
|
||||
if ($abiVersion -ne 2) { $errors.Add("ABI version is not 2.") }
|
||||
if ($type -ne 0xfe10) { $errors.Add("ELF type is not ET_SCE_EXEC_ASLR (0xfe10).") }
|
||||
if ($phentsize -ne 0x38) { $errors.Add("Program-header size is not 0x38.") }
|
||||
if ($null -eq $procParam) { $errors.Add("PT_SCE_PROCPARAM is missing.") }
|
||||
if ($null -eq $linkingLoad) { $errors.Add("The flags-zero linking LOAD is missing.") }
|
||||
foreach ($load in $mappedLoads) {
|
||||
if ($load.Align -ne 0x4000) { $errors.Add("Mapped LOAD $($load.Index) is not 0x4000 aligned.") }
|
||||
if ($load.MemorySize -lt $load.FileSize) { $errors.Add("LOAD $($load.Index) has memsz smaller than filesz.") }
|
||||
if ($load.Flags -notin @(1, 4, 6)) { $errors.Add("LOAD $($load.Index) has unsupported flags $($load.Flags).") }
|
||||
}
|
||||
foreach ($header in $programHeaders) {
|
||||
if ($header.Type -eq 0x6fffff00) {
|
||||
if ($header.MemorySize -ne 0) {
|
||||
$errors.Add("Comment header $($header.Index) must have zero memsz.")
|
||||
}
|
||||
if ($header.Align -ne 0x10) {
|
||||
$errors.Add("Comment header $($header.Index) must use 0x10 alignment.")
|
||||
}
|
||||
}
|
||||
if ($header.Type -eq 4 -and $header.Address -eq 0 -and
|
||||
$header.MemorySize -ne 0) {
|
||||
$errors.Add("Unmapped NOTE header $($header.Index) must have zero memsz.")
|
||||
}
|
||||
}
|
||||
|
||||
$companion = $null
|
||||
$sdk = $null
|
||||
$procMagic = $null
|
||||
if (-not $container -and $null -ne $procParam -and $elf + $procParam + 0x60 -le $data.Length) {
|
||||
$procMagic = [Text.Encoding]::ASCII.GetString($data, $elf + [int]$procParam + 8, 4)
|
||||
$companion = U32 ($elf + [int]$procParam + 0x10)
|
||||
$sdk = U32 ($elf + [int]$procParam + 0x14)
|
||||
if ($procMagic -ne "ORBI") { $errors.Add("Process-parameter magic is not ORBI.") }
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
Layer = "ELF"
|
||||
Container = $container
|
||||
OsAbi = $osAbi
|
||||
AbiVersion = $abiVersion
|
||||
Type = Hex $type
|
||||
Entry = Hex $entry
|
||||
ProgramHeaders = $phnum
|
||||
MappedLoads = $mappedLoads.Count
|
||||
LinkingLoadIndex = $linkingLoad
|
||||
ProcessParamMagic = $procMagic
|
||||
CompanionVersion = if ($null -ne $companion) { Hex $companion } else { $null }
|
||||
SdkVersion = if ($null -ne $sdk) { Hex $sdk } else { $null }
|
||||
StaticErrors = $errors.Count
|
||||
} | Format-List
|
||||
|
||||
if ($errors.Count) {
|
||||
$errors | ForEach-Object { Write-Error $_ }
|
||||
exit 1
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ checked=0
|
||||
for file in "${repository_files[@]}"; do
|
||||
[[ -f $file ]] || continue
|
||||
case "$file" in
|
||||
*.c|*.cc|*.cpp|*.h|*.hpp|*.ld|*.py|*.ps1|*.sh|*.yml|*.yaml|Makefile|.clang-format|.clang-tidy|.env.example)
|
||||
*.c|*.cc|*.cpp|*.h|*.hpp|*.ld|*.py|*.sh|*.yml|*.yaml|Makefile|.clang-format|.clang-tidy|.env.example)
|
||||
header=$(head -n 20 "$file")
|
||||
grep -Fq ps5-native-app-boilerplate <<<"$header"
|
||||
grep -Fq 'Copyright (C) 2026 BlackBearReloaded' <<<"$header"
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
<#
|
||||
ps5-native-app-boilerplate - Presentation asset preparation.
|
||||
Copyright (C) 2026 BlackBearReloaded
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
Converts developer-owned artwork and audio into the supported launcher
|
||||
asset profile, or validates the current sce_sys presentation files.
|
||||
#>
|
||||
|
||||
#requires -Version 5.1
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$ValidateOnly,
|
||||
[string]$Icon = "",
|
||||
[string]$Background = "",
|
||||
[string]$SelectionBackground = "",
|
||||
[string]$LaunchBackground = "",
|
||||
[string]$Audio = "",
|
||||
[string]$Texconv = "",
|
||||
[string]$Ffmpeg = "",
|
||||
[string]$At9Tool = "",
|
||||
[ValidateRange(0, 86400)]
|
||||
[double]$AudioStart = 0,
|
||||
[ValidateRange(0.1, 87.3)]
|
||||
[double]$AudioDuration = 15,
|
||||
[string]$OutputDirectory = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repo = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
if (-not $OutputDirectory) {
|
||||
$OutputDirectory = Join-Path $repo "sce_sys"
|
||||
}
|
||||
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
|
||||
|
||||
function Fail([string]$Message) {
|
||||
throw "prepare-assets: $Message"
|
||||
}
|
||||
|
||||
function Read-U16([byte[]]$Bytes, [int]$Offset) {
|
||||
if ($Offset -lt 0 -or $Offset + 2 -gt $Bytes.Length) {
|
||||
Fail "Unexpected end of file."
|
||||
}
|
||||
return [BitConverter]::ToUInt16($Bytes, $Offset)
|
||||
}
|
||||
|
||||
function Read-U32([byte[]]$Bytes, [int]$Offset) {
|
||||
if ($Offset -lt 0 -or $Offset + 4 -gt $Bytes.Length) {
|
||||
Fail "Unexpected end of file."
|
||||
}
|
||||
return [BitConverter]::ToUInt32($Bytes, $Offset)
|
||||
}
|
||||
|
||||
function Read-U32BigEndian([byte[]]$Bytes, [int]$Offset) {
|
||||
if ($Offset -lt 0 -or $Offset + 4 -gt $Bytes.Length) {
|
||||
Fail "Unexpected end of file."
|
||||
}
|
||||
return ([uint32]$Bytes[$Offset] -shl 24) -bor
|
||||
([uint32]$Bytes[$Offset + 1] -shl 16) -bor
|
||||
([uint32]$Bytes[$Offset + 2] -shl 8) -bor
|
||||
[uint32]$Bytes[$Offset + 3]
|
||||
}
|
||||
|
||||
function Read-Ascii([byte[]]$Bytes, [int]$Offset, [int]$Count) {
|
||||
if ($Offset -lt 0 -or $Offset + $Count -gt $Bytes.Length) {
|
||||
Fail "Unexpected end of file."
|
||||
}
|
||||
return [Text.Encoding]::ASCII.GetString($Bytes, $Offset, $Count)
|
||||
}
|
||||
|
||||
function Resolve-Input([string]$Path, [string]$Label) {
|
||||
if (-not $Path -or -not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
||||
Fail "$Label file not found: $Path"
|
||||
}
|
||||
return (Resolve-Path -LiteralPath $Path).Path
|
||||
}
|
||||
|
||||
function Resolve-Executable([string]$Path, [string[]]$Names, [string]$Help) {
|
||||
if ($Path) {
|
||||
if (Test-Path -LiteralPath $Path -PathType Leaf) {
|
||||
return (Resolve-Path -LiteralPath $Path).Path
|
||||
}
|
||||
$command = Get-Command $Path -ErrorAction SilentlyContinue
|
||||
if ($command) {
|
||||
return $command.Source
|
||||
}
|
||||
Fail "Executable not found: $Path"
|
||||
}
|
||||
foreach ($name in $Names) {
|
||||
$command = Get-Command $name -ErrorAction SilentlyContinue
|
||||
if ($command) {
|
||||
return $command.Source
|
||||
}
|
||||
}
|
||||
Fail $Help
|
||||
}
|
||||
|
||||
function Invoke-Checked([string]$Executable, [string[]]$Arguments, [string]$Label) {
|
||||
& $Executable @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "$Label failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-BackgroundAsset(
|
||||
[string]$Source,
|
||||
[string]$Label,
|
||||
[string]$Stem,
|
||||
[string]$WorkDirectory,
|
||||
[string]$Destination,
|
||||
[string]$TexconvPath
|
||||
) {
|
||||
$resolvedSource = Resolve-Input $Source $Label
|
||||
$assetWork = Join-Path $WorkDirectory $Stem
|
||||
New-Item -ItemType Directory -Path $assetWork | Out-Null
|
||||
Invoke-Checked $TexconvPath @("-nologo", "-y", "-w", "3840", "-h", "2160", "-m", "1",
|
||||
"-f", "R8G8B8A8_UNORM", "-ft", "png", "-o", $assetWork, $resolvedSource) `
|
||||
"$Label preview conversion"
|
||||
$preview = Get-ChildItem -LiteralPath $assetWork -File |
|
||||
Where-Object { $_.Extension -ieq ".png" } | Select-Object -First 1
|
||||
if (-not $preview) {
|
||||
Fail "texconv did not produce the $Label preview."
|
||||
}
|
||||
Invoke-Checked $TexconvPath @("-nologo", "-y", "-w", "3840", "-h", "2160", "-m", "1",
|
||||
"-f", "BC7_UNORM", "-dx10", "-o", $assetWork, $preview.FullName) "$Label DDS conversion"
|
||||
$dds = Get-ChildItem -LiteralPath $assetWork -File |
|
||||
Where-Object { $_.Extension -ieq ".dds" } | Select-Object -First 1
|
||||
if (-not $dds) {
|
||||
Fail "texconv did not produce the $Label BC7 DDS."
|
||||
}
|
||||
[IO.File]::Copy($dds.FullName, (Join-Path $Destination "$Stem.dds"), $true)
|
||||
return [PSCustomObject]@{ Preview = $preview.FullName; Dds = $dds.FullName }
|
||||
}
|
||||
|
||||
function Test-Png([string]$Path, [int]$ExpectedWidth, [int]$ExpectedHeight) {
|
||||
$bytes = [IO.File]::ReadAllBytes($Path)
|
||||
$signature = [byte[]](137, 80, 78, 71, 13, 10, 26, 10)
|
||||
if ($bytes.Length -lt 24) {
|
||||
Fail "$Path is too small to be a PNG."
|
||||
}
|
||||
for ($i = 0; $i -lt $signature.Length; $i++) {
|
||||
if ($bytes[$i] -ne $signature[$i]) {
|
||||
Fail "$Path is not a PNG file."
|
||||
}
|
||||
}
|
||||
$width = Read-U32BigEndian $bytes 16
|
||||
$height = Read-U32BigEndian $bytes 20
|
||||
if ($width -ne $ExpectedWidth -or $height -ne $ExpectedHeight) {
|
||||
Fail "$Path must be ${ExpectedWidth}x${ExpectedHeight}; found ${width}x${height}."
|
||||
}
|
||||
}
|
||||
|
||||
function Test-Dds([string]$Path) {
|
||||
$bytes = [IO.File]::ReadAllBytes($Path)
|
||||
if ($bytes.Length -lt 148 -or (Read-Ascii $bytes 0 4) -ne "DDS ") {
|
||||
Fail "$Path is not a DDS file."
|
||||
}
|
||||
$headerSize = Read-U32 $bytes 4
|
||||
$height = Read-U32 $bytes 12
|
||||
$width = Read-U32 $bytes 16
|
||||
$mipCount = Read-U32 $bytes 28
|
||||
$fourCc = Read-Ascii $bytes 84 4
|
||||
$format = Read-U32 $bytes 128
|
||||
$dimension = Read-U32 $bytes 132
|
||||
$arraySize = Read-U32 $bytes 140
|
||||
$expectedSize = [long]148 + ([long]$width / 4) * ([long]$height / 4) * 16
|
||||
if ($headerSize -ne 124 -or $width -ne 3840 -or $height -ne 2160 -or
|
||||
$mipCount -ne 1 -or $fourCc -ne "DX10" -or $format -ne 98 -or
|
||||
$dimension -ne 3 -or $arraySize -ne 1 -or $bytes.Length -ne $expectedSize) {
|
||||
Fail "$Path must be a single 3840x2160 BC7_UNORM (98) DX10 2D DDS without mipmaps."
|
||||
}
|
||||
}
|
||||
|
||||
function Test-At9([string]$Path) {
|
||||
$bytes = [IO.File]::ReadAllBytes($Path)
|
||||
if ($bytes.Length -lt 128 -or (Read-Ascii $bytes 0 4) -ne "RIFF" -or
|
||||
(Read-Ascii $bytes 8 4) -ne "WAVE" -or (Read-U32 $bytes 4) + 8 -ne $bytes.Length) {
|
||||
Fail "$Path is not a complete RIFF/WAVE file."
|
||||
}
|
||||
|
||||
$chunks = @{}
|
||||
$position = 12
|
||||
while ($position + 8 -le $bytes.Length) {
|
||||
$id = Read-Ascii $bytes $position 4
|
||||
$size = [int](Read-U32 $bytes ($position + 4))
|
||||
$payload = $position + 8
|
||||
if ($size -lt 0 -or $payload + $size -gt $bytes.Length) {
|
||||
Fail "$Path contains a truncated RIFF chunk."
|
||||
}
|
||||
$chunks[$id] = @($payload, $size)
|
||||
$position = $payload + $size + ($size % 2)
|
||||
}
|
||||
foreach ($required in @("fmt ", "fact", "smpl", "data")) {
|
||||
if (-not $chunks.ContainsKey($required)) {
|
||||
Fail "$Path is missing the required RIFF '$required' chunk."
|
||||
}
|
||||
}
|
||||
|
||||
$fmt = [int]$chunks["fmt "][0]
|
||||
$smpl = [int]$chunks["smpl"][0]
|
||||
if ([int]$chunks["fmt "][1] -lt 40 -or [int]$chunks["smpl"][1] -lt 32) {
|
||||
Fail "$Path contains undersized ATRAC9 format or loop metadata."
|
||||
}
|
||||
$formatGuid = "D242E147BA368D4D88FC61654F8C836C"
|
||||
$actualGuid = -join ($bytes[($fmt + 24)..($fmt + 39)] | ForEach-Object { $_.ToString("X2") })
|
||||
$channels = Read-U16 $bytes ($fmt + 2)
|
||||
$byteRate = Read-U32 $bytes ($fmt + 8)
|
||||
$loops = Read-U32 $bytes ($smpl + 28)
|
||||
if ((Read-U16 $bytes $fmt) -ne 0xFFFE -or $channels -notin @(1, 2) -or
|
||||
(Read-U32 $bytes ($fmt + 4)) -ne 48000 -or $byteRate -le 0 -or
|
||||
$byteRate -gt ($channels * 12000) -or $actualGuid -ne $formatGuid -or
|
||||
$loops -lt 1 -or $bytes.Length -gt 2097152) {
|
||||
Fail "$Path must be looped 48 kHz ATRAC9 at no more than 96 kb/s mono or 192 kb/s stereo, and no larger than 2 MiB."
|
||||
}
|
||||
}
|
||||
|
||||
function Test-AssetSet([string]$Directory) {
|
||||
$iconPath = Join-Path $Directory "icon0.png"
|
||||
if (-not (Test-Path -LiteralPath $iconPath -PathType Leaf)) {
|
||||
Fail "Required launcher icon not found: $iconPath"
|
||||
}
|
||||
Test-Png $iconPath 512 512
|
||||
|
||||
$pic0 = Join-Path $Directory "pic0.dds"
|
||||
$pic1 = Join-Path $Directory "pic1.dds"
|
||||
$hasPic0 = Test-Path -LiteralPath $pic0 -PathType Leaf
|
||||
$hasPic1 = Test-Path -LiteralPath $pic1 -PathType Leaf
|
||||
if ($hasPic0 -xor $hasPic1) {
|
||||
Fail "Supply both pic0.dds and pic1.dds, or neither."
|
||||
}
|
||||
if ($hasPic0) {
|
||||
Test-Dds $pic0
|
||||
Test-Dds $pic1
|
||||
}
|
||||
|
||||
$sound = Join-Path $Directory "snd0.at9"
|
||||
if (Test-Path -LiteralPath $sound -PathType Leaf) {
|
||||
Test-At9 $sound
|
||||
}
|
||||
Write-Host "Presentation assets validated: $Directory"
|
||||
}
|
||||
|
||||
if ($ValidateOnly) {
|
||||
Test-AssetSet $OutputDirectory
|
||||
return
|
||||
}
|
||||
if ($Background -and ($SelectionBackground -or $LaunchBackground)) {
|
||||
Fail "Use -Background by itself, or use -SelectionBackground and -LaunchBackground for distinct artwork."
|
||||
}
|
||||
if (-not $Icon -and -not $Background -and -not $SelectionBackground -and
|
||||
-not $LaunchBackground -and -not $Audio) {
|
||||
Fail "Supply an icon, background, or audio input, or use -ValidateOnly."
|
||||
}
|
||||
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
|
||||
Fail "Conversion uses Windows texconv and an optional Windows ATRAC9 encoder; run this command from Windows PowerShell."
|
||||
}
|
||||
|
||||
$root = [IO.Path]::GetPathRoot($OutputDirectory)
|
||||
if ($OutputDirectory.TrimEnd('\', '/') -eq $root.TrimEnd('\', '/')) {
|
||||
Fail "Refusing to use a filesystem root as the output directory."
|
||||
}
|
||||
New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
|
||||
$work = Join-Path ([IO.Path]::GetTempPath()) ("ps5-native-assets-" + [Guid]::NewGuid().ToString("N"))
|
||||
New-Item -ItemType Directory -Path $work | Out-Null
|
||||
|
||||
try {
|
||||
if ($Icon -or $Background -or $SelectionBackground -or $LaunchBackground) {
|
||||
$texconvPath = Resolve-Executable $Texconv @("texconv.exe", "texconv") `
|
||||
"texconv was not found. Install it with: winget install Microsoft.DirectXTex.Texconv"
|
||||
}
|
||||
|
||||
if ($Icon) {
|
||||
$iconSource = Resolve-Input $Icon "Icon"
|
||||
$iconWork = Join-Path $work "icon"
|
||||
New-Item -ItemType Directory -Path $iconWork | Out-Null
|
||||
Invoke-Checked $texconvPath @("-nologo", "-y", "-w", "512", "-h", "512", "-m", "1",
|
||||
"-f", "R8G8B8A8_UNORM", "-ft", "png", "-o", $iconWork, $iconSource) "Icon conversion"
|
||||
$converted = Get-ChildItem -LiteralPath $iconWork -File |
|
||||
Where-Object { $_.Extension -ieq ".png" } | Select-Object -First 1
|
||||
if (-not $converted) {
|
||||
Fail "texconv did not produce the launcher PNG."
|
||||
}
|
||||
[IO.File]::Copy($converted.FullName, (Join-Path $OutputDirectory "icon0.png"), $true)
|
||||
}
|
||||
|
||||
if ($Background) {
|
||||
$convertedBackground = Convert-BackgroundAsset $Background "Background" "pic0" $work `
|
||||
$OutputDirectory $texconvPath
|
||||
[IO.File]::Copy($convertedBackground.Dds, (Join-Path $OutputDirectory "pic1.dds"), $true)
|
||||
[IO.File]::Copy($convertedBackground.Preview,
|
||||
(Join-Path $OutputDirectory "background-source.png"), $true)
|
||||
[IO.File]::Copy($convertedBackground.Preview,
|
||||
(Join-Path $OutputDirectory "launch-background-source.png"), $true)
|
||||
} else {
|
||||
if ($SelectionBackground) {
|
||||
$convertedSelection = Convert-BackgroundAsset $SelectionBackground `
|
||||
"Selection background" "pic0" $work $OutputDirectory $texconvPath
|
||||
[IO.File]::Copy($convertedSelection.Preview,
|
||||
(Join-Path $OutputDirectory "background-source.png"), $true)
|
||||
}
|
||||
if ($LaunchBackground) {
|
||||
$convertedLaunch = Convert-BackgroundAsset $LaunchBackground `
|
||||
"Launch background" "pic1" $work $OutputDirectory $texconvPath
|
||||
[IO.File]::Copy($convertedLaunch.Preview,
|
||||
(Join-Path $OutputDirectory "launch-background-source.png"), $true)
|
||||
}
|
||||
}
|
||||
|
||||
if ($Audio) {
|
||||
$audioSource = Resolve-Input $Audio "Audio"
|
||||
$soundOutput = Join-Path $OutputDirectory "snd0.at9"
|
||||
if ([IO.Path]::GetExtension($audioSource) -ieq ".at9") {
|
||||
if ($audioSource -ne $soundOutput) {
|
||||
[IO.File]::Copy($audioSource, $soundOutput, $true)
|
||||
}
|
||||
} else {
|
||||
$at9ToolPath = Resolve-Executable $At9Tool @("ps4_at9tool.exe") `
|
||||
"A compatible ATRAC9 encoder was not found. Supply your legally obtained ps4_at9tool.exe with -At9Tool."
|
||||
$wav = Join-Path $work "selection.wav"
|
||||
$startText = $AudioStart.ToString([Globalization.CultureInfo]::InvariantCulture)
|
||||
$durationText = $AudioDuration.ToString([Globalization.CultureInfo]::InvariantCulture)
|
||||
$ffmpegArgs = @("-hide_banner", "-loglevel", "error", "-y", "-ss", $startText, "-i", $audioSource,
|
||||
"-t", $durationText, "-map_metadata", "-1", "-af", "loudnorm=I=-28:LRA=11:TP=-2",
|
||||
"-ar", "48000", "-ac", "2", "-c:a", "pcm_s16le", $wav)
|
||||
$ffmpegCommand = if ($Ffmpeg) {
|
||||
Resolve-Executable $Ffmpeg @() "FFmpeg was not found."
|
||||
} else {
|
||||
$command = Get-Command ffmpeg.exe, ffmpeg -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($command) { $command.Source } else { "" }
|
||||
}
|
||||
if ($ffmpegCommand) {
|
||||
Invoke-Checked $ffmpegCommand $ffmpegArgs "Audio preparation"
|
||||
} elseif (Get-Command wsl.exe -ErrorAction SilentlyContinue) {
|
||||
& wsl.exe --exec sh -lc "command -v ffmpeg >/dev/null 2>&1"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "FFmpeg was not found on Windows or in WSL. Install it in WSL with: sudo apt install ffmpeg"
|
||||
}
|
||||
function To-WslPath([string]$Path) {
|
||||
if ($Path -notmatch '^([A-Za-z]):\\(.*)$') {
|
||||
Fail "WSL FFmpeg requires source files on a Windows drive: $Path"
|
||||
}
|
||||
return "/mnt/$($Matches[1].ToLowerInvariant())/$($Matches[2].Replace('\', '/'))"
|
||||
}
|
||||
$wslArgs = @($ffmpegArgs)
|
||||
$wslArgs[7] = To-WslPath $audioSource
|
||||
$wslArgs[$wslArgs.Count - 1] = To-WslPath $wav
|
||||
Invoke-Checked "wsl.exe" (@("--exec", "ffmpeg") + $wslArgs) "WSL audio preparation"
|
||||
} else {
|
||||
Fail "FFmpeg was not found. Install it or pass its executable with -Ffmpeg."
|
||||
}
|
||||
$encoded = Join-Path $work "snd0.at9"
|
||||
Invoke-Checked $at9ToolPath @("-e", "-br", "192", "-wholeloop", $wav, $encoded) `
|
||||
"ATRAC9 encoding"
|
||||
[IO.File]::Copy($encoded, $soundOutput, $true)
|
||||
}
|
||||
|
||||
$paramPath = Join-Path $OutputDirectory "param.json"
|
||||
if (Test-Path -LiteralPath $paramPath -PathType Leaf) {
|
||||
$param = Get-Content -LiteralPath $paramPath -Raw | ConvertFrom-Json
|
||||
$param.pubtools | Add-Member -NotePropertyName loudnessSnd0 -NotePropertyValue "-28.00" -Force
|
||||
$json = $param | ConvertTo-Json -Depth 16
|
||||
[IO.File]::WriteAllText($paramPath, $json + [Environment]::NewLine,
|
||||
[Text.UTF8Encoding]::new($false))
|
||||
}
|
||||
}
|
||||
|
||||
Test-AssetSet $OutputDirectory
|
||||
} finally {
|
||||
$resolvedWork = [IO.Path]::GetFullPath($work)
|
||||
$resolvedTemp = [IO.Path]::GetFullPath([IO.Path]::GetTempPath())
|
||||
if ($resolvedWork.StartsWith($resolvedTemp, [StringComparison]::OrdinalIgnoreCase) -and
|
||||
(Test-Path -LiteralPath $resolvedWork -PathType Container)) {
|
||||
Remove-Item -LiteralPath $resolvedWork -Recurse -Force
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# ps5-native-app-boilerplate - Bash presentation asset preparation frontend.
|
||||
# Copyright (C) 2026 BlackBearReloaded
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Provides Linux/WSL-style arguments for the Windows DirectXTex and ATRAC9
|
||||
# conversion workflow while keeping validation available on native Linux.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
output_directory="$root/sce_sys"
|
||||
validate_only=0
|
||||
conversion_inputs=0
|
||||
declare -a forwarded=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
usage: tools/prepare-assets.sh [options]
|
||||
|
||||
--icon PATH Prepare sce_sys/icon0.png
|
||||
--background PATH Use one image for pic0.dds and pic1.dds
|
||||
--selection-background PATH Prepare pic0.dds from distinct artwork
|
||||
--launch-background PATH Prepare pic1.dds from distinct artwork
|
||||
--audio PATH Copy ready AT9 or prepare supported source audio
|
||||
--audio-start SECONDS Start of the audio excerpt (default: 0)
|
||||
--audio-duration SECONDS Excerpt duration (default: 15; maximum: 87.3)
|
||||
--texconv COMMAND texconv.exe name or path
|
||||
--ffmpeg COMMAND Windows FFmpeg name or path
|
||||
--at9-tool COMMAND Legally obtained compatible ATRAC9 encoder
|
||||
--output-directory PATH Destination (default: sce_sys)
|
||||
--validate-only Validate current files on Linux or WSL
|
||||
-h, --help Show this help
|
||||
|
||||
Conversion runs through Windows PowerShell and therefore requires WSL or a
|
||||
Windows Bash environment. Validation is portable and requires only Python 3.
|
||||
EOF
|
||||
}
|
||||
|
||||
need_value() {
|
||||
[[ $# -ge 2 && -n $2 ]] || {
|
||||
echo "missing value for $1" >&2
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--icon|--background|--selection-background|--launch-background|--audio)
|
||||
need_value "$@"
|
||||
forwarded+=("$1" "$2")
|
||||
((conversion_inputs += 1))
|
||||
shift 2
|
||||
;;
|
||||
--texconv|--ffmpeg|--at9-tool|--audio-start|--audio-duration)
|
||||
need_value "$@"
|
||||
forwarded+=("$1" "$2")
|
||||
shift 2
|
||||
;;
|
||||
--output-directory)
|
||||
need_value "$@"
|
||||
output_directory=$2
|
||||
shift 2
|
||||
;;
|
||||
--validate-only)
|
||||
validate_only=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "unknown option: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ((validate_only)); then
|
||||
((${#forwarded[@]} == 0)) || {
|
||||
echo "--validate-only cannot be combined with conversion options" >&2
|
||||
exit 2
|
||||
}
|
||||
exec bash "$root/tools/validate-assets.sh" "$output_directory"
|
||||
fi
|
||||
((conversion_inputs > 0)) || {
|
||||
echo "supply an icon, background, or audio input, or use --validate-only" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
if command -v powershell.exe >/dev/null 2>&1; then
|
||||
powershell_command=$(command -v powershell.exe)
|
||||
elif command -v powershell >/dev/null 2>&1; then
|
||||
powershell_command=$(command -v powershell)
|
||||
else
|
||||
echo "conversion requires Windows PowerShell through WSL or Windows Bash" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
to_windows_path() {
|
||||
local path=$1
|
||||
case "$path" in
|
||||
[A-Za-z]:\\*) printf '%s\n' "$path"; return ;;
|
||||
esac
|
||||
if command -v wslpath >/dev/null 2>&1; then
|
||||
wslpath -w -- "$(realpath -m -- "$path")"
|
||||
elif command -v cygpath >/dev/null 2>&1; then
|
||||
cygpath -w -- "$path"
|
||||
else
|
||||
echo "cannot translate path for Windows: $path" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
to_windows_command() {
|
||||
local command=$1
|
||||
if [[ -e $command || $command == */* ]]; then
|
||||
to_windows_path "$command"
|
||||
else
|
||||
printf '%s\n' "$command"
|
||||
fi
|
||||
}
|
||||
|
||||
script_path=$(to_windows_path "$root/tools/prepare-assets.ps1")
|
||||
declare -a powershell_args=(-NoProfile -ExecutionPolicy Bypass -File "$script_path")
|
||||
for ((index = 0; index < ${#forwarded[@]}; index += 2)); do
|
||||
option=${forwarded[index]}
|
||||
value=${forwarded[index + 1]}
|
||||
case "$option" in
|
||||
--icon) powershell_args+=(-Icon "$(to_windows_path "$value")") ;;
|
||||
--background) powershell_args+=(-Background "$(to_windows_path "$value")") ;;
|
||||
--selection-background) powershell_args+=(-SelectionBackground "$(to_windows_path "$value")") ;;
|
||||
--launch-background) powershell_args+=(-LaunchBackground "$(to_windows_path "$value")") ;;
|
||||
--audio) powershell_args+=(-Audio "$(to_windows_path "$value")") ;;
|
||||
--texconv) powershell_args+=(-Texconv "$(to_windows_command "$value")") ;;
|
||||
--ffmpeg) powershell_args+=(-Ffmpeg "$(to_windows_command "$value")") ;;
|
||||
--at9-tool) powershell_args+=(-At9Tool "$(to_windows_command "$value")") ;;
|
||||
--audio-start) powershell_args+=(-AudioStart "$value") ;;
|
||||
--audio-duration) powershell_args+=(-AudioDuration "$value") ;;
|
||||
esac
|
||||
done
|
||||
powershell_args+=(-OutputDirectory "$(to_windows_path "$output_directory")")
|
||||
|
||||
"$powershell_command" "${powershell_args[@]}"
|
||||
@@ -1,126 +0,0 @@
|
||||
<#
|
||||
ps5-native-app-boilerplate - Clean-room runtime-shim reproducer.
|
||||
Copyright (C) 2026 BlackBearReloaded
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
Rebuilds twice with the native C++ tools and installs the shim only after
|
||||
deterministic hash and attribution checks pass.
|
||||
#>
|
||||
|
||||
#requires -Version 5.1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
$native = Join-Path $root "tooling/native"
|
||||
$setupNativeDependencies = Join-Path $root "tools/setup-native-dependencies.ps1"
|
||||
$apiManifest = Join-Path $root "tooling/native/runtime/api-surface.txt"
|
||||
$importManifest = Join-Path $root "tooling/native/runtime/imports.txt"
|
||||
$work = Join-Path $root "build/runtime-shim"
|
||||
$rawA = Join-Path $work "libc-a.raw.elf"
|
||||
$rawB = Join-Path $work "libc-b.raw.elf"
|
||||
$signedA = Join-Path $work "libc-a.prx"
|
||||
$signedB = Join-Path $work "libc-b.prx"
|
||||
$builder = Join-Path $work "libc-builder"
|
||||
$tool = Join-Path $work "ps5-native-tool"
|
||||
$output = Join-Path $root "runtime/libc.prx"
|
||||
$manifest = Join-Path $root "runtime/libc.prx.sha256"
|
||||
$expectedRaw = "8EE6E124993E1AF26420CB455890FD002F5D6C7E78883C860CE45734E7D002BB"
|
||||
$expectedSigned = "E6FF45D16ADF687855CC3B33B0C8A4132B6504360B221E0A34C7E99FB3BA0036"
|
||||
|
||||
function Fail([string]$Message) {
|
||||
throw "ps5-native-app-boilerplate: $Message"
|
||||
}
|
||||
|
||||
function WslPath([string]$Path) {
|
||||
$absolute = [IO.Path]::GetFullPath($Path)
|
||||
if ($absolute -notmatch '^([A-Za-z]):\\(.*)$') {
|
||||
Fail "Path is not on a Windows drive visible to WSL: $absolute"
|
||||
}
|
||||
return "/mnt/$($Matches[1].ToLowerInvariant())/$($Matches[2].Replace('\', '/'))"
|
||||
}
|
||||
|
||||
function Invoke-Wsl([string[]]$Arguments) {
|
||||
& wsl.exe --exec @Arguments
|
||||
if ($LASTEXITCODE -ne 0) { Fail "Native runtime build failed." }
|
||||
}
|
||||
|
||||
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) {
|
||||
Fail "WSL was not found."
|
||||
}
|
||||
foreach ($required in @($apiManifest, $importManifest,
|
||||
$manifest,
|
||||
$setupNativeDependencies,
|
||||
(Join-Path $native "libc_builder.cpp"),
|
||||
(Join-Path $native "native_app_builder.cpp"))) {
|
||||
if (-not (Test-Path -LiteralPath $required -PathType Leaf)) {
|
||||
Fail "Required source file not found: $required"
|
||||
}
|
||||
}
|
||||
|
||||
$recordedSigned = ((Get-Content -LiteralPath $manifest -Raw).Trim() -split '\s+')[0]
|
||||
if ($recordedSigned -ne $expectedSigned) {
|
||||
Fail "The runtime checksum manifest does not match the release digest."
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $work -Force | Out-Null
|
||||
$dependencyJson = & $setupNativeDependencies -SkipSdk
|
||||
$nativeDependencies = ($dependencyJson -join "`n") | ConvertFrom-Json
|
||||
$zlibInclude = [string]$nativeDependencies.zlibInclude
|
||||
$zlibArchive = [string]$nativeDependencies.zlibArchive
|
||||
if (-not $zlibInclude -or -not $zlibArchive) {
|
||||
Fail "Native dependency bootstrap returned incomplete zlib paths."
|
||||
}
|
||||
|
||||
Invoke-Wsl @("clang++", "-std=c++20", "-O2", "-Wall", "-Wextra", "-Werror",
|
||||
(WslPath (Join-Path $native "libc_builder.cpp")), "-o", (WslPath $builder))
|
||||
Invoke-Wsl @("clang++", "-std=c++20", "-O2", "-Wall", "-Wextra", "-Werror",
|
||||
"-I", $zlibInclude,
|
||||
(WslPath (Join-Path $native "native_app_builder.cpp")),
|
||||
(WslPath (Join-Path $native "self_container.cpp")),
|
||||
(WslPath (Join-Path $native "elf_object.cpp")),
|
||||
(WslPath (Join-Path $native "sce_module_writer.cpp")),
|
||||
$zlibArchive, "-o", (WslPath $tool))
|
||||
|
||||
Invoke-Wsl @((WslPath $builder), (WslPath $apiManifest),
|
||||
(WslPath $importManifest), (WslPath $rawA))
|
||||
Invoke-Wsl @((WslPath $builder), (WslPath $apiManifest),
|
||||
(WslPath $importManifest), (WslPath $rawB))
|
||||
$rawHashA = (Get-FileHash -LiteralPath $rawA -Algorithm SHA256).Hash
|
||||
$rawHashB = (Get-FileHash -LiteralPath $rawB -Algorithm SHA256).Hash
|
||||
if ($rawHashA -ne $rawHashB -or $rawHashA -ne $expectedRaw) {
|
||||
Fail "The raw module does not match the deterministic release artifact."
|
||||
}
|
||||
|
||||
Invoke-Wsl @((WslPath $tool), "self", "--sign", "--in", (WslPath $rawA),
|
||||
"--out", (WslPath $signedA))
|
||||
Invoke-Wsl @((WslPath $tool), "self", "--sign", "--in", (WslPath $rawB),
|
||||
"--out", (WslPath $signedB))
|
||||
$signedHashA = (Get-FileHash -LiteralPath $signedA -Algorithm SHA256).Hash
|
||||
$signedHashB = (Get-FileHash -LiteralPath $signedB -Algorithm SHA256).Hash
|
||||
if ($signedHashA -ne $signedHashB -or $signedHashA -ne $expectedSigned) {
|
||||
Fail "The signed module does not match the deterministic release artifact."
|
||||
}
|
||||
if ((Get-Item -LiteralPath $signedA).Length -ne 1284674) {
|
||||
Fail "The signed module is not 1,284,674 bytes."
|
||||
}
|
||||
|
||||
foreach ($artifact in @($rawA, $signedA)) {
|
||||
$text = [Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($artifact))
|
||||
if (-not $text.Contains("BlackBearReloaded")) {
|
||||
Fail "Generated output is missing the attribution marker."
|
||||
}
|
||||
foreach ($forbidden in @("W:/Build", "J013", "Prospero_Release", "sys/internal")) {
|
||||
if ($text.Contains($forbidden)) {
|
||||
Fail "Generated output contains forbidden historical text: $forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[IO.File]::Copy($signedA, $output, $true)
|
||||
$installedHash = (Get-FileHash -LiteralPath $output -Algorithm SHA256).Hash
|
||||
if ($installedHash -ne $expectedSigned) {
|
||||
Fail "The installed runtime does not match the release digest."
|
||||
}
|
||||
Write-Host "Rebuilt deterministic clean-room runtime module with native C++ tools."
|
||||
Write-Host "Raw SHA-256: $rawHashA"
|
||||
Write-Host "Signed SHA-256: $signedHashA"
|
||||
Write-Host "Output: $output"
|
||||
@@ -1,43 +0,0 @@
|
||||
<#
|
||||
ps5-native-app-boilerplate - Optional UFS2Tool bootstrapper.
|
||||
Copyright (C) 2026 BlackBearReloaded
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
Delegates to the shared WSL bootstrapper, which fetches and builds a pinned
|
||||
UFS2Tool checkout inside the ignored dependency cache.
|
||||
#>
|
||||
|
||||
#requires -Version 5.1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
$setup = Join-Path $root "tools/setup-packaging-dependencies.sh"
|
||||
|
||||
function Fail([string]$Message) {
|
||||
throw "ps5-native-app-boilerplate: $Message"
|
||||
}
|
||||
|
||||
function WslPath([string]$Path) {
|
||||
$absolute = [IO.Path]::GetFullPath($Path)
|
||||
if ($absolute -notmatch '^([A-Za-z]):\\(.*)$') {
|
||||
Fail "Path is not on a Windows drive visible to WSL: $absolute"
|
||||
}
|
||||
return "/mnt/$($Matches[1].ToLowerInvariant())/$($Matches[2].Replace('\', '/'))"
|
||||
}
|
||||
|
||||
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) {
|
||||
Fail "WSL was not found."
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $setup -PathType Leaf)) {
|
||||
Fail "Shared packaging bootstrapper was not found: $setup"
|
||||
}
|
||||
|
||||
$binary = & wsl.exe --exec bash (WslPath $setup) ffpkg
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "Unable to fetch and build UFS2Tool."
|
||||
}
|
||||
if ($binary -is [array]) { $binary = $binary[-1] }
|
||||
if (-not $binary) {
|
||||
Fail "UFS2Tool bootstrapper did not return its runner path."
|
||||
}
|
||||
|
||||
Write-Output $binary.Trim()
|
||||
@@ -1,106 +0,0 @@
|
||||
<#
|
||||
ps5-native-app-boilerplate - Optional MkPFS packaging bootstrapper.
|
||||
Copyright (C) 2026 BlackBearReloaded
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
Fetches a pinned MkPFS checkout and installs it in an ignored virtual env.
|
||||
#>
|
||||
|
||||
#requires -Version 5.1
|
||||
param(
|
||||
[string]$Python = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
$dependencyRoot = Join-Path $root ".deps"
|
||||
$checkout = Join-Path $dependencyRoot "MkPFS"
|
||||
$repository = "https://github.com/PSBrew/MkPFS.git"
|
||||
$revision = "6cb8313dfe0c988ac52617794553f343243d3a56"
|
||||
|
||||
function Fail([string]$Message) {
|
||||
throw "ps5-native-app-boilerplate: $Message"
|
||||
}
|
||||
|
||||
function Invoke-Git([string[]]$Arguments) {
|
||||
& git @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "Git command failed: git $($Arguments -join ' ')"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
||||
Fail "Git was not found. Install Git for Windows and retry."
|
||||
}
|
||||
|
||||
if (-not $Python) {
|
||||
$pythonCommand = Get-Command python -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$pythonCommand = Get-Command $Python -ErrorAction SilentlyContinue
|
||||
}
|
||||
if (-not $pythonCommand) {
|
||||
Fail "Python 3.9 or newer was not found. Install Python or pass -Python C:\path\to\python.exe."
|
||||
}
|
||||
$Python = $pythonCommand.Source
|
||||
$pythonVersion = (& $Python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')").Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or [version]$pythonVersion -lt [version]"3.9") {
|
||||
Fail "MkPFS requires Python 3.9 or newer; found $pythonVersion."
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $checkout)) {
|
||||
New-Item -ItemType Directory -Path $dependencyRoot -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $checkout | Out-Null
|
||||
Invoke-Git @("-C", $checkout, "init", "--quiet")
|
||||
Invoke-Git @("-C", $checkout, "config", "core.autocrlf", "false")
|
||||
Invoke-Git @("-C", $checkout, "remote", "add", "origin", $repository)
|
||||
Invoke-Git @("-C", $checkout, "fetch", "--quiet", "--depth", "1", "origin", $revision)
|
||||
Invoke-Git @("-C", $checkout, "checkout", "--quiet", "--detach", "FETCH_HEAD")
|
||||
} elseif (-not (Test-Path -LiteralPath (Join-Path $checkout ".git") -PathType Container)) {
|
||||
Fail "$checkout exists but is not a managed Git checkout."
|
||||
}
|
||||
|
||||
$actualRevision = (& git -C $checkout rev-parse HEAD).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or $actualRevision -ne $revision) {
|
||||
Fail "MkPFS cache is at $actualRevision; expected $revision. Remove .deps/MkPFS and retry."
|
||||
}
|
||||
& git -C $checkout diff --quiet
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "MkPFS cache has local changes. Remove .deps/MkPFS and retry."
|
||||
}
|
||||
|
||||
foreach ($required in @("pyproject.toml", "LICENSE", "mkpfs/__main__.py")) {
|
||||
$requiredPath = Join-Path $checkout $required
|
||||
if (-not (Test-Path -LiteralPath $requiredPath -PathType Leaf)) {
|
||||
Fail "Required MkPFS file not found: $requiredPath"
|
||||
}
|
||||
}
|
||||
|
||||
$venv = Join-Path $checkout ".venv"
|
||||
$venvPython = Join-Path $venv "Scripts/python.exe"
|
||||
$stamp = Join-Path $venv ".boilerplate-revision"
|
||||
if (-not (Test-Path -LiteralPath $venvPython -PathType Leaf)) {
|
||||
& $Python -m venv $venv
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "Unable to create the MkPFS virtual environment."
|
||||
}
|
||||
}
|
||||
$installedRevision = if (Test-Path -LiteralPath $stamp -PathType Leaf) {
|
||||
(Get-Content -LiteralPath $stamp -Raw).Trim()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
if ($installedRevision -ne $revision) {
|
||||
& $venvPython -m pip install --disable-pip-version-check $checkout | Out-Host
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "MkPFS dependency installation failed."
|
||||
}
|
||||
Set-Content -LiteralPath $stamp -Value $revision -Encoding ascii
|
||||
}
|
||||
|
||||
& $venvPython -m mkpfs --help *> $null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "The cached MkPFS command failed its startup check."
|
||||
}
|
||||
|
||||
Write-Host "MkPFS ready at revision $revision."
|
||||
Write-Output $venvPython
|
||||
@@ -1,63 +0,0 @@
|
||||
<#
|
||||
ps5-native-app-boilerplate - Native dependency bootstrapper.
|
||||
Copyright (C) 2026 BlackBearReloaded
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
Runs the canonical Linux/WSL dependency bootstrap and returns its paths as
|
||||
JSON for PowerShell callers.
|
||||
#>
|
||||
|
||||
#requires -Version 5.1
|
||||
param(
|
||||
[switch]$SkipSdk
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
|
||||
function Fail([string]$Message) {
|
||||
throw "ps5-native-app-boilerplate: $Message"
|
||||
}
|
||||
|
||||
function Convert-ToWslPath([string]$Path) {
|
||||
$absolute = [IO.Path]::GetFullPath($Path)
|
||||
if ($absolute -notmatch '^([A-Za-z]):\\(.*)$') {
|
||||
Fail "The repository must be on a Windows drive visible to WSL."
|
||||
}
|
||||
return "/mnt/$($Matches[1].ToLowerInvariant())/$($Matches[2].Replace('\', '/'))"
|
||||
}
|
||||
|
||||
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) {
|
||||
Fail "WSL was not found."
|
||||
}
|
||||
|
||||
$script = Convert-ToWslPath (Join-Path $root "tools/setup-native-dependencies.sh")
|
||||
$arguments = @("--exec", "bash", $script)
|
||||
if ($SkipSdk) {
|
||||
$arguments += "--skip-sdk"
|
||||
}
|
||||
$lines = @(& wsl.exe @arguments)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "Native dependency bootstrap failed."
|
||||
}
|
||||
|
||||
$values = @{}
|
||||
foreach ($line in $lines) {
|
||||
if ($line -match '^([A-Z_]+)=(.*)$') {
|
||||
$values[$Matches[1]] = $Matches[2]
|
||||
}
|
||||
}
|
||||
foreach ($name in @("ZLIB_INCLUDE", "ZLIB_ARCHIVE")) {
|
||||
if (-not $values.ContainsKey($name)) {
|
||||
Fail "Native dependency bootstrap did not report $name."
|
||||
}
|
||||
}
|
||||
if (-not $SkipSdk -and -not $values.ContainsKey("SDK_ROOT")) {
|
||||
Fail "Native dependency bootstrap did not report SDK_ROOT."
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
sdkRoot = if ($SkipSdk) { "" } else { $values["SDK_ROOT"] }
|
||||
zlibInclude = $values["ZLIB_INCLUDE"]
|
||||
zlibArchive = $values["ZLIB_ARCHIVE"]
|
||||
} | ConvertTo-Json -Compress
|
||||
Reference in new issue
Block a user