Init foo_input_joc
build / windows (push) Has been cancelled

This commit is contained in:
2026-09-25 17:07:04 +08:00
commit ad62a914e6
127 changed files with 38545 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
# Builds foo_input_joc.dll with the SDK's own MSBuild projects.
#
# pwsh -File tools/build.ps1 # Win32 (foobar2000 1.6 and 2.x 32-bit)
# pwsh -File tools/build.ps1 -Platform x64 # x64 (foobar2000 2.x 64-bit)
#
# Release-Static is the SDK configuration that uses the static CRT, which is what
# this component requires: no runtime redistributable, and the same CRT model as
# the rendering core it links.
[CmdletBinding()]
param(
[ValidateSet('Win32', 'x64')][string]$Platform = 'Win32',
[string]$Configuration = 'Release-Static'
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$project = Join-Path $projectRoot 'foo_input_joc.vcxproj'
function Find-MSBuild {
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
if (Test-Path -LiteralPath $vswhere) {
$found = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild `
-find 'MSBuild\**\Bin\MSBuild.exe' 2>$null
if ($found) { return ($found | Select-Object -First 1) }
}
foreach ($edition in @('Community', 'Professional', 'Enterprise', 'BuildTools')) {
$candidate = Join-Path $env:ProgramFiles "Microsoft Visual Studio\2022\$edition\MSBuild\Current\Bin\MSBuild.exe"
if (Test-Path -LiteralPath $candidate) { return $candidate }
}
throw 'MSBuild.exe not found; install the Visual Studio C++ workload'
}
if (-not (Test-Path -LiteralPath (Join-Path $projectRoot 'SDK\foobar2000\SDK\foobar2000-versions.h'))) {
throw 'SDK missing; run tools/setup_sdk.ps1 first'
}
$msbuild = Find-MSBuild
$logDir = Join-Path $projectRoot 'build'
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
$log = Join-Path $logDir "msbuild.$Platform.log"
# Redirection (not a pipeline) keeps msbuild's own exit code in $LASTEXITCODE.
& $msbuild $project /nologo /m /v:minimal "/p:Configuration=$Configuration" "/p:Platform=$Platform" *> $log
$code = $LASTEXITCODE
Get-Content -LiteralPath $log -Tail 40
if ($code -ne 0) {
Write-Host "BUILD FAILED: msbuild exit code $code (full log: $log)" -ForegroundColor Red
exit $code
}
$dll = Join-Path $projectRoot "build\$Platform\foo_input_joc.dll"
if (-not (Test-Path -LiteralPath $dll)) {
Write-Host "BUILD FAILED: msbuild reported success but $dll is missing" -ForegroundColor Red
exit 1
}
Write-Host ("BUILD OK: {0} ({1} bytes)" -f $dll, (Get-Item -LiteralPath $dll).Length) -ForegroundColor Green
+55
View File
@@ -0,0 +1,55 @@
# Builds the headless test tools.
#
# pwsh -File tools/build_tests.ps1 # x64 (matches the core's own build)
# pwsh -File tools/build_tests.ps1 -Platform Win32 # x86 (the architecture foobar2000 1.6 uses)
#
# They link the kernel archive that the component itself links
# (kernel\joc_kernel.vcxproj -> build\kernel-<Platform>\joc_kernel.lib), so what
# they exercise is the same code the component plays with.
[CmdletBinding()]
param(
[ValidateSet('Win32', 'x64')][string]$Platform = 'x64'
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$vsRoot = 'C:\Program Files\Microsoft Visual Studio\2022\Community'
$vcvars = if ($Platform -eq 'x64') {
Join-Path $vsRoot 'VC\Auxiliary\Build\vcvars64.bat'
} else {
Join-Path $vsRoot 'VC\Auxiliary\Build\vcvars32.bat'
}
$coreLib = Join-Path $projectRoot "build\kernel-$Platform\joc_kernel.lib"
foreach ($tool in @($vcvars, $coreLib)) {
if (-not (Test-Path -LiteralPath $tool)) {
throw "missing: $tool (build kernel\joc_kernel.vcxproj for $Platform first)"
}
}
$outDir = Join-Path $projectRoot "build\tests-$Platform"
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
$bat = Join-Path $outDir 'build.bat'
# JOC_STATIC / EJOC_STATIC: the kernel headers must not decorate their entry
# points as imported, because the tools link the archive directly.
$common = "/nologo /std:c++20 /EHsc /O2 /MT /W4 /utf-8 /D_CRT_SECURE_NO_WARNINGS /DJOC_STATIC /DEJOC_STATIC /I src /I kernel\include /I kernel\src"
$lines = @(
'@echo off',
"call `"$vcvars`" >nul",
"cd /d `"$projectRoot`"",
# log.cpp comes along because the engine writes its diagnostics through it.
"cl $common /Fe:`"$outDir\scan_selftest.exe`" /Fo:`"$outDir\\`" tests\scan_selftest.cpp src\eac3_scan.cpp",
"cl $common /Fe:`"$outDir\prefs_layout_check.exe`" /Fo:`"$outDir\\`" tests\prefs_layout_check.cpp user32.lib gdi32.lib",
"cl $common /Fe:`"$outDir\scan_crosscheck.exe`" /Fo:`"$outDir\\`" tests\scan_crosscheck.cpp src\eac3_scan.cpp `"$coreLib`" shell32.lib",
"cl $common /Fe:`"$outDir\render_harness.exe`" /Fo:`"$outDir\\`" tests\render_harness.cpp src\joc_decode.cpp src\eac3_scan.cpp src\log.cpp `"$coreLib`" shell32.lib",
'if errorlevel 1 exit /b 1',
'exit /b 0'
)
Set-Content -LiteralPath $bat -Value $lines -Encoding ASCII
& cmd /c $bat
if ($LASTEXITCODE -ne 0) { throw "test build failed with exit code $LASTEXITCODE" }
Get-ChildItem $outDir -Filter '*.exe' | ForEach-Object {
Write-Host ("built {0} ({1} bytes)" -f $_.Name, $_.Length) -ForegroundColor Green
}
+53
View File
@@ -0,0 +1,53 @@
# Installs the built component into a portable foobar2000 test bed.
#
# pwsh -File tools/deploy.ps1 -TestBed ..\fb2k-test
# pwsh -File tools/deploy.ps1 -TestBed D:\fb2k -Platform x64
#
# Where the component has to go depends on the foobar2000 layout, and getting it
# wrong is silent: foobar2000 simply never calls LoadLibrary on the file. Two
# layouts are in the wild:
#
# profile-relative <app>\profile\user-components\<name>\<name>.dll
# foobar2000 1.6.19 portable and 2.x
# app-relative <app>\user-components\<name>\<name>.dll
# older / repacked 1.6 installs whose profile is <app>\configuration
#
# The per-component subdirectory is required in both: a DLL lying directly in
# user-components\ is not scanned. Installing a .fb2k-component package through
# foobar2000 itself ends up doing the same thing.
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)][string]$TestBed,
[ValidateSet('Win32', 'x64')][string]$Platform = 'Win32',
[string]$Target
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$dll = Join-Path $projectRoot "build\$Platform\foo_input_joc.dll"
if (-not (Test-Path -LiteralPath $dll)) { throw "build the $Platform DLL first: $dll is missing" }
if (-not (Test-Path -LiteralPath (Join-Path $TestBed 'foobar2000.exe'))) {
throw "no foobar2000.exe in $TestBed"
}
if (-not $Target) {
$appRelative = Test-Path -LiteralPath (Join-Path $TestBed 'configuration')
$root = if ($appRelative) { $TestBed } else { Join-Path $TestBed 'profile' }
$Target = Join-Path $root 'user-components'
Write-Host ("layout: {0} ({1})" -f $(if ($appRelative) { 'app-relative' } else { 'profile-relative' }), $root)
}
# Portable profile: config stays inside the test bed instead of %APPDATA%.
New-Item -ItemType File -Force -Path (Join-Path $TestBed 'portable_mode_enabled') | Out-Null
$componentDir = Join-Path $Target 'foo_input_joc'
New-Item -ItemType Directory -Force -Path $componentDir | Out-Null
Copy-Item -LiteralPath $dll -Destination (Join-Path $componentDir 'foo_input_joc.dll') -Force
# Drop the log of a previous run so what gets read next is this run only.
$log = Join-Path $componentDir 'joc_decoder.log'
if (Test-Path -LiteralPath $log) { Remove-Item -LiteralPath $log -Force }
Write-Host ("deployed {0} -> {1}" -f $dll, (Join-Path $componentDir 'foo_input_joc.dll')) -ForegroundColor Green
Write-Host "log will be written to: $log"
+68
View File
@@ -0,0 +1,68 @@
# Packages the component the way foobar2000 expects to install it.
#
# pwsh -File tools/package.ps1 # both architectures
# pwsh -File tools/package.ps1 -Platform x64
#
# A ".fb2k-component" file is a plain ZIP archive holding the component DLL; the
# user drags it onto foobar2000 (or uses Preferences -> Components -> Install) and
# the installer unpacks it into a subfolder of the profile's user-components. The
# archive must therefore contain the DLL at its root, and the architecture matters:
# foobar2000 1.6 is 32-bit, 2.x ships both, and a wrong-architecture DLL is
# silently ignored -- so x86 and x64 get separate packages.
#
# A readme travels inside the archive so the file explains itself once installed.
[CmdletBinding()]
param(
[ValidateSet('Win32', 'x64', 'both')][string]$Platform = 'both',
[string]$Configuration = 'Release-Static'
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$distDir = Join-Path $projectRoot 'dist'
# The version the component reports comes from main.cpp; read it from the source so
# the package name cannot drift from what foobar2000 shows.
$version = '0.0.0'
$main = Get-Content (Join-Path $projectRoot 'src\main.cpp') -Raw
if ($main -match '#define\s+JOC_VERSION\s+"([^"]+)"') {
$version = $Matches[1]
} elseif ($main -match 'DECLARE_COMPONENT_VERSION\s*\(\s*"[^"]*"\s*,\s*"([^"]+)"') {
$version = $Matches[1]
}
$targets = if ($Platform -eq 'both') { @('Win32', 'x64') } else { @($Platform) }
New-Item -ItemType Directory -Force -Path $distDir | Out-Null
# Packages of earlier versions would otherwise pile up and be uploaded as well.
Get-ChildItem $distDir -Filter '*.fb2k-component' -ErrorAction SilentlyContinue |
Where-Object { $_.Name -notlike "*$version*" } |
Remove-Item -Force
foreach ($target in $targets) {
$architecture = if ($target -eq 'Win32') { 'x86' } else { 'x64' }
Write-Host "building $target ($architecture)..." -ForegroundColor Cyan
& (Join-Path $PSScriptRoot 'build.ps1') -Platform $target -Configuration $Configuration | Out-Null
if ($LASTEXITCODE -ne 0) { throw "build failed for $target" }
$dll = Join-Path $projectRoot "build\$target\foo_input_joc.dll"
if (-not (Test-Path -LiteralPath $dll)) { throw "missing $dll" }
# Stage exactly what should end up in the user's profile.
$stage = Join-Path $distDir "stage-$architecture"
if (Test-Path -LiteralPath $stage) { Remove-Item -LiteralPath $stage -Recurse -Force }
New-Item -ItemType Directory -Force -Path $stage | Out-Null
Copy-Item -LiteralPath $dll -Destination (Join-Path $stage 'foo_input_joc.dll') -Force
Copy-Item -LiteralPath (Join-Path $projectRoot 'README.md') -Destination $stage -Force
$archive = Join-Path $distDir "foo_input_joc-$version-$architecture.fb2k-component"
if (Test-Path -LiteralPath $archive) { Remove-Item -LiteralPath $archive -Force }
Compress-Archive -Path (Join-Path $stage '*') -DestinationPath $archive -CompressionLevel Optimal
Remove-Item -LiteralPath $stage -Recurse -Force
Write-Host (" {0} ({1:N0} bytes)" -f $archive, (Get-Item -LiteralPath $archive).Length) -ForegroundColor Green
}
Write-Host ''
Get-ChildItem $distDir -Filter '*.fb2k-component' | ForEach-Object {
Write-Host ("{0} sha256={1}" -f $_.Name, (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower())
}
+70
View File
@@ -0,0 +1,70 @@
# Runs a portable test bed unattended: start it, let it work, exit cleanly, dump the log.
#
# pwsh -File tools/run.ps1 -TestBed ..\fb2k-test -Play ..\sample.eac3
# pwsh -File tools/run.ps1 -TestBed D:\fb2k -Platform x64 -Play D:\x.eac3 -WaitSeconds 25
#
# The test bed is an existing portable foobar2000 installation with the component
# deployed into it (tools/deploy.ps1). Two things this script exists to get right:
#
# * foobar2000 is closed with /exit, never force-killed. A force-killed instance
# leaves <profile>\running behind, and every later start then reports "another
# foobar2000 instance appears to be running with the same configuration data"
# and idles without loading any user component.
# * the component's log is written next to the DLL, so it is read from wherever
# the component was deployed.
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)][string]$TestBed,
[ValidateSet('Win32', 'x64')][string]$Platform = 'Win32',
[string[]]$Play,
[int]$WaitSeconds = 15,
[switch]$KeepRunning
)
$ErrorActionPreference = 'Stop'
if (-not (Test-Path -LiteralPath (Join-Path $TestBed 'foobar2000.exe'))) {
throw "no foobar2000.exe in $TestBed"
}
$exe = Join-Path $TestBed 'foobar2000.exe'
# A leftover "running" marker makes the next start refuse to load components.
$running = Join-Path $TestBed 'profile\running'
if (Test-Path -LiteralPath $running) {
if (Get-Process foobar2000 -ErrorAction SilentlyContinue) {
throw 'a foobar2000 process is running; close it (or /exit) before starting a run'
}
Remove-Item -LiteralPath $running -Force
Write-Host 'cleared a stale profile\running marker'
}
$arguments = @()
if ($Play) { $arguments += $Play }
$process = Start-Process -FilePath $exe -ArgumentList $arguments -PassThru
Write-Host "started pid=$($process.Id) $($Play -join ', ')"
Start-Sleep -Seconds $WaitSeconds
if (-not $KeepRunning) {
& $exe /exit | Out-Null
Start-Sleep -Seconds 6
$left = Get-Process foobar2000 -ErrorAction SilentlyContinue
if ($left) {
# A component that still holds a decode thread can keep /exit from
# finishing. Kill it, and clear the marker that the kill leaves behind,
# or the next start will refuse to load user components.
Write-Host 'exit did not finish; terminating and clearing the profile marker' -ForegroundColor Yellow
$left | Stop-Process -Force
Start-Sleep -Seconds 2
if (Test-Path -LiteralPath $running) { Remove-Item -LiteralPath $running -Force }
}
}
Write-Host '--- component log ---' -ForegroundColor Cyan
$logs = Get-ChildItem $TestBed -Recurse -Filter 'joc_decoder.log' -ErrorAction SilentlyContinue
if (-not $logs) {
Write-Host 'no joc_decoder.log found: the component never ran' -ForegroundColor Yellow
}
foreach ($log in $logs) {
Write-Host "# $($log.FullName)"
Get-Content -LiteralPath $log.FullName
}
+81
View File
@@ -0,0 +1,81 @@
# Fetches the official foobar2000 SDK into <project>\SDK and pins the target API level.
#
# pwsh -File tools/setup_sdk.ps1 # latest SDK, target 80 (foobar2000 1.5/1.6)
# pwsh -File tools/setup_sdk.ps1 -TargetVersion 81
#
# The SDK is a .7z from the official site (https://www.foobar2000.org/SDK). Its
# foobar2000-versions.h carries a commented-out FOOBAR2000_TARGET_VERSION 80 line:
# switching to it is the SDK's documented way of declaring a 1.6-compatible
# component, so this script performs exactly that edit and verifies it.
#
# The archive itself is only a download cache and is kept outside the project (in
# the system temporary directory): the build needs the extracted SDK, never the
# archive. Delete the cache at any time to force a fresh download.
[CmdletBinding()]
param(
[string]$Version = '2026-09-17',
[int]$TargetVersion = 80,
[switch]$Force
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$sdkDir = Join-Path $projectRoot 'SDK'
$dlDir = Join-Path ([System.IO.Path]::GetTempPath()) 'foo_input_joc-sdk'
$archive = Join-Path $dlDir "SDK-$Version.7z"
$url = "https://www.foobar2000.org/downloads/SDK-$Version.7z"
function Find-SevenZip {
$candidates = @(
(Join-Path $env:ProgramFiles '7-Zip\7z.exe'),
(Join-Path ${env:ProgramFiles(x86)} '7-Zip\7z.exe')
)
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate) { return $candidate }
}
$command = Get-Command 7z -ErrorAction SilentlyContinue
if ($command) { return $command.Source }
throw 'no 7-Zip found; install it or unpack the SDK manually into <project>\SDK'
}
New-Item -ItemType Directory -Force -Path $dlDir | Out-Null
if ((Test-Path -LiteralPath (Join-Path $sdkDir 'foobar2000\SDK\foobar2000-versions.h')) -and -not $Force) {
Write-Host "SDK already present in $sdkDir (pass -Force to re-fetch)"
} else {
if (-not (Test-Path -LiteralPath $archive)) {
Write-Host "downloading $url"
Invoke-WebRequest -Uri $url -OutFile $archive -UseBasicParsing -TimeoutSec 300
}
Write-Host ("archive {0} ({1} bytes)" -f $archive, (Get-Item -LiteralPath $archive).Length)
if (Test-Path -LiteralPath $sdkDir) { Remove-Item -LiteralPath $sdkDir -Recurse -Force }
New-Item -ItemType Directory -Force -Path $sdkDir | Out-Null
& (Find-SevenZip) x $archive "-o$sdkDir" -y | Out-Null
if ($LASTEXITCODE -ne 0) { throw "7-Zip failed with exit code $LASTEXITCODE" }
}
$versionsHeader = Join-Path $sdkDir 'foobar2000\SDK\foobar2000-versions.h'
if (-not (Test-Path -LiteralPath $versionsHeader)) { throw "missing $versionsHeader" }
# Only the Windows section is touched: it is the one carrying the "// 2.0" marker.
$text = Get-Content -LiteralPath $versionsHeader -Raw
$patched = $text
if ($TargetVersion -eq 80) {
$patched = $patched -replace '(?m)^//\s*#define FOOBAR2000_TARGET_VERSION 80 // 1\.5, 1\.6', '#define FOOBAR2000_TARGET_VERSION 80 // 1.5, 1.6'
$patched = $patched -replace '(?m)^#define FOOBAR2000_TARGET_VERSION 81 // 2\.0', '// #define FOOBAR2000_TARGET_VERSION 81 // 2.0'
} else {
$patched = $patched -replace '(?m)^#define FOOBAR2000_TARGET_VERSION 80 // 1\.5, 1\.6', '// #define FOOBAR2000_TARGET_VERSION 80 // 1.5, 1.6'
$patched = $patched -replace '(?m)^//\s*#define FOOBAR2000_TARGET_VERSION 81 // 2\.0', '#define FOOBAR2000_TARGET_VERSION 81 // 2.0'
}
if ($patched -ne $text) {
Set-Content -LiteralPath $versionsHeader -Value $patched -NoNewline
Write-Host "patched $versionsHeader for target $TargetVersion"
}
# The active Windows-level define must be the one we asked for.
$win = [regex]::Match((Get-Content -LiteralPath $versionsHeader -Raw),
'(?s)#ifdef _WIN32(.*?)#else // _WIN32').Groups[1].Value
if ($win -notmatch "(?m)^#define FOOBAR2000_TARGET_VERSION $TargetVersion") {
throw "could not pin FOOBAR2000_TARGET_VERSION to $TargetVersion; check `$versionsHeader"
}
Write-Host "OK: SDK $Version in $sdkDir, FOOBAR2000_TARGET_VERSION=$TargetVersion"