# Build the Switch NRO from Windows using devkitPro's MSYS2 toolchain. param( [string]$DevkitProPath = '', [switch]$KeepBuild ) $ErrorActionPreference = 'Stop' function Quote-Bash([string]$Value) { return "'" + $Value.Replace("'", "'\''") + "'" } $projectRoot = [IO.Path]::GetFullPath($PSScriptRoot) if (-not $DevkitProPath) { if ($env:DEVKITPRO -and (Test-Path -LiteralPath $env:DEVKITPRO)) { $DevkitProPath = $env:DEVKITPRO } else { $DevkitProPath = 'C:\devkitPro' } } $devkitRoot = [IO.Path]::GetFullPath($DevkitProPath) $bash = Join-Path $devkitRoot 'msys2\usr\bin\bash.exe' $cygpath = Join-Path $devkitRoot 'msys2\usr\bin\cygpath.exe' $rules = Join-Path $devkitRoot 'libnx\switch_rules' $compiler = Join-Path $devkitRoot 'devkitA64\bin\aarch64-none-elf-g++.exe' $pkgConfig = Join-Path $devkitRoot 'portlibs\switch\bin\aarch64-none-elf-pkg-config' foreach ($required in @($bash, $cygpath, $rules, $compiler, $pkgConfig)) { if (-not (Test-Path -LiteralPath $required)) { throw "Missing Switch build dependency: $required`nInstall devkitPro's Switch toolchain and switch-pkg-config packages; see README.md." } } if ($devkitRoot -match '\s') { throw 'The devkitPro installation path contains spaces; install it at C:\devkitPro or choose a path without spaces.' } $tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()) if ($tempRoot -match '\s') { throw 'The temporary path contains spaces. Set TEMP to a writable directory without spaces before building.' } # The upstream devkitPro Makefile pattern requires a path without spaces, so # compile a fresh copy in the temp folder. $stage = Join-Path $tempRoot ('switchnote-build-' + [Guid]::NewGuid().ToString('N')) $resolvedStage = [IO.Path]::GetFullPath($stage) $tempPrefix = $tempRoot.TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar if (-not $resolvedStage.StartsWith($tempPrefix, [StringComparison]::OrdinalIgnoreCase) -or -not ([IO.Path]::GetFileName($resolvedStage) -match '^switchnote-build-[0-9a-f]{32}$')) { throw "Unexpected temporary build path: $resolvedStage" } New-Item -ItemType Directory -Path $stage | Out-Null try { foreach ($entry in @('Makefile', 'source', 'include', 'third_party', 'romfs')) { $source = Join-Path $projectRoot $entry if (-not (Test-Path -LiteralPath $source)) { throw "Project file or directory is missing: $source" } Copy-Item -LiteralPath $source -Destination (Join-Path $stage $entry) -Recurse } $rootJpeg = Join-Path $projectRoot 'icon.jpg' $rootPng = Join-Path $projectRoot 'icon.png' if (Test-Path -LiteralPath $rootPng) { try { Add-Type -AssemblyName System.Drawing $inputImage = [Drawing.Image]::FromFile($rootPng) $bitmap = [Drawing.Bitmap]::new(256, 256) $graphics = [Drawing.Graphics]::FromImage($bitmap) try { $graphics.Clear([Drawing.Color]::White) $graphics.InterpolationMode = [Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic $graphics.DrawImage($inputImage, 0, 0, 256, 256) $bitmap.Save((Join-Path $stage 'icon.jpg'), [Drawing.Imaging.ImageFormat]::Jpeg) } finally { $graphics.Dispose() $bitmap.Dispose() $inputImage.Dispose() } } catch { Write-Warning "Could not convert icon.png to JPEG; the default libnx icon will be used. $($_.Exception.Message)" } } elseif (Test-Path -LiteralPath $rootJpeg) { Copy-Item -LiteralPath $rootJpeg -Destination (Join-Path $stage 'icon.jpg') } $devkitUnix = (& $cygpath -u $devkitRoot).Trim() if ($LASTEXITCODE -ne 0 -or -not $devkitUnix) { throw 'cygpath could not translate the devkitPro path.' } $stageUnix = (& $cygpath -u $stage).Trim() if ($LASTEXITCODE -ne 0 -or -not $stageUnix) { throw 'cygpath could not translate the build path.' } $devkitQuoted = Quote-Bash $devkitUnix $stageQuoted = Quote-Bash $stageUnix $command = 'export DEVKITPRO={0}; export DEVKITA64="$DEVKITPRO/devkitA64"; export PATH="$DEVKITA64/bin:$DEVKITPRO/tools/bin:$DEVKITPRO/portlibs/switch/bin:$PATH"; cd {1}; make -j2' -f $devkitQuoted, $stageQuoted & $bash -lc $command if ($LASTEXITCODE -ne 0) { throw "Switch build failed with exit code $LASTEXITCODE." } $builtNro = Join-Path $stage 'switchnote.nro' if (-not (Test-Path -LiteralPath $builtNro)) { throw "Build reported success but no NRO exists at $builtNro" } $destination = Join-Path $projectRoot 'switchnote.nro' Copy-Item -LiteralPath $builtNro -Destination $destination -Force Write-Host "Built $destination" } finally { if ($KeepBuild) { Write-Host "Temporary build retained at $stage" } elseif (Test-Path -LiteralPath $stage) { # This directory was created above and its full path was checked against TEMP. Remove-Item -LiteralPath $stage -Recurse -Force } }