Files
Deshab-OS/build.ps1
T

1085 lines
43 KiB
PowerShell
Raw Normal View History

2026-08-09 22:14:01 +08:00
param(
2026-08-29 18:47:54 +08:00
[ValidateSet('dev','release','realtest','both')][string]$Variant = 'both',
[switch]$Vhd
2026-08-09 22:14:01 +08:00
)
$ErrorActionPreference = 'Stop'
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
$UtsmDir = Join-Path $Root 'CODE\UTSM'
$DskDir = Join-Path $Root 'CODE\dsk'
$SystemDir = Join-Path $Root 'SYSTEM'
$IsoDir = Join-Path $Root 'ISO'
2026-08-09 22:14:01 +08:00
$ConfigsDir = Join-Path $Root 'build\configs'
$Output = Join-Path $SystemDir 'boot\utsm.elf'
$DskOutput = Join-Path $SystemDir 'system\deshab64\deshab.elf'
2026-08-09 22:14:01 +08:00
$DevImagePath = Join-Path $IsoDir 'deshab-dev.img'
$ReleaseImagePath = Join-Path $IsoDir 'deshab-release.img'
$RealtestImagePath = Join-Path $IsoDir 'deshab-realtest.img'
$BuildTmp = Join-Path $Root '.build_tmp'
# Legacy single-FAT32 ESP size helper (New-GptFat32Image). Dual-partition
# packing uses CODE/linux/pack_system_image.sh and sizes each partition
# from SYSTEM/boot+EFI+limine vs the rest.
function Get-EspSizeMBFromSystem([string]$SourceDir) {
$sum = (Get-ChildItem -LiteralPath $SourceDir -Recurse -File -Force |
Measure-Object -Property Length -Sum).Sum
if (-not $sum) { $sum = 0 }
$contentMB = [int][Math]::Ceiling($sum / 1MB)
$need = $contentMB + [int][Math]::Ceiling($contentMB * 0.15) + 256
if ($need -lt 2048) { $need = 2048 }
return $need
}
if ($env:ESP_SIZE_MB) {
$EspSizeMB = [int]$env:ESP_SIZE_MB
} else {
$EspSizeMB = Get-EspSizeMBFromSystem $SystemDir
2026-08-09 22:14:01 +08:00
}
function ConvertTo-WslUnixPath([string]$WinPath) {
$full = [System.IO.Path]::GetFullPath($WinPath)
if ($full -match '^(?<d>[A-Za-z]):\\(?<rest>.*)$') {
$drive = $Matches['d'].ToLowerInvariant()
$rest = $Matches['rest'] -replace '\\', '/'
return "/mnt/$drive/$rest"
}
throw "Cannot map Windows path to WSL: $WinPath"
}
# GPT p1 FAT32 ESP (/EFI /limine /boot) + GPT p2 ext4 (rest of SYSTEM/).
function New-GptFat32Ext4Image([string]$SourceDir, [string]$ImagePath) {
$scriptWin = Join-Path $Root 'CODE\linux\pack_system_image.sh'
if (-not (Test-Path -LiteralPath $scriptWin)) {
throw "Missing packer: $scriptWin"
}
if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) {
throw 'WSL is required to pack GPT FAT32+ext4 (mkfs.ext4).'
}
$imgDir = Split-Path -Parent $ImagePath
if (-not (Test-Path -LiteralPath $imgDir)) {
New-Item -ItemType Directory -Path $imgDir | Out-Null
}
$wslScript = ConvertTo-WslUnixPath $scriptWin
$wslSys = ConvertTo-WslUnixPath $SourceDir
$wslImg = "$(ConvertTo-WslUnixPath $imgDir)/$([System.IO.Path]::GetFileName($ImagePath))"
Write-Host '[build] Packing GPT: FAT32 ESP (/EFI /limine /boot) + ext4 data'
& wsl -u root -- bash $wslScript $wslSys $wslImg
if ($LASTEXITCODE -ne 0) {
throw "pack_system_image.sh failed ($LASTEXITCODE)"
}
}
2026-08-09 22:14:01 +08:00
# Optional persistent rw volume for Linux guest (Phase 4: overlayfs upperdir).
# When SYSTEM/boot/linux-extra-rootfs.img is present, build.ps1 prints a notice;
# limine.conf is expected to declare it as a boot module (added in Phase 4).
$ExtraRootfsPath = Join-Path $SystemDir 'boot\linux-extra-rootfs.img'
function Find-Tool($Name, $ExtraNames = @()) {
$names = @($Name) + $ExtraNames
foreach ($n in $names) {
$cmd = Get-Command $n -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
}
$dirs = @(
'C:\Program Files\LLVM\bin',
'C:\Program Files (x86)\LLVM\bin',
'C:\LLVM\bin',
'D:\LLVM\bin',
'D:\Program Files\LLVM\bin',
'D:\Code\LLVM\bin',
'C:\msys64\clang64\bin',
'C:\msys64\mingw64\bin',
'C:\mingw64\bin'
)
foreach ($dir in $dirs) {
foreach ($n in $names) {
$candidate = Join-Path $dir $n
if (Test-Path $candidate) { return $candidate }
if (-not $n.EndsWith('.exe')) {
$candidateExe = Join-Path $dir "$n.exe"
if (Test-Path $candidateExe) { return $candidateExe }
}
}
}
return $null
}
function Build-DkmDriver([string]$SourcePath, [string]$OutputPath) {
$obj = Join-Path $BuildTmp (([System.IO.Path]::GetFileNameWithoutExtension($SourcePath)) + '.o')
$outDir = Split-Path -Parent $OutputPath
if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Force -Path $outDir | Out-Null }
& $clang -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fno-pic -fno-pie -mcmodel=kernel -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -O2 -c $SourcePath -o $obj
if ($LASTEXITCODE -ne 0) { throw "failed to compile DKM driver: $SourcePath" }
& $lld -r -o $OutputPath $obj
if ($LASTEXITCODE -ne 0) { throw "failed to link DKM driver: $OutputPath" }
Write-Host "[build] DKM driver Output: $OutputPath"
}
function Set-Le16([byte[]]$Buffer, [int]$Offset, [int]$Value) {
$Buffer[$Offset] = [byte]($Value -band 0xff)
$Buffer[$Offset + 1] = [byte](($Value -shr 8) -band 0xff)
}
function Set-Le32([byte[]]$Buffer, [int]$Offset, [UInt64]$Value) {
$Buffer[$Offset] = [byte]($Value -band 0xff)
$Buffer[$Offset + 1] = [byte](($Value -shr 8) -band 0xff)
$Buffer[$Offset + 2] = [byte](($Value -shr 16) -band 0xff)
$Buffer[$Offset + 3] = [byte](($Value -shr 24) -band 0xff)
}
function Set-Le64([byte[]]$Buffer, [int]$Offset, [UInt64]$Value) {
for ($i = 0; $i -lt 8; $i++) {
$Buffer[$Offset + $i] = [byte](($Value -shr ($i * 8)) -band 0xff)
}
}
function Write-At($Stream, [Int64]$Offset, [byte[]]$Data) {
[void]$Stream.Seek($Offset, [System.IO.SeekOrigin]::Begin)
$Stream.Write($Data, 0, $Data.Length)
}
function Get-Crc32([byte[]]$Data) {
$crc = [uint32]4294967295
foreach ($b in $Data) {
$crc = $crc -bxor [uint32]$b
for ($i = 0; $i -lt 8; $i++) {
if (($crc -band 1) -ne 0) {
$crc = [uint32](($crc -shr 1) -bxor 0xedb88320)
} else {
$crc = [uint32]($crc -shr 1)
}
}
}
return [uint32]($crc -bxor [uint32]4294967295)
}
function Test-ShortFatName([string]$Name) {
if ($Name -cmatch '[a-z]') { return $false }
$parts = $Name.Split('.')
if ($parts.Count -gt 2) { return $false }
if ($parts[0].Length -lt 1 -or $parts[0].Length -gt 8) { return $false }
if ($parts.Count -eq 2 -and $parts[1].Length -gt 3) { return $false }
return ($Name -cmatch '^[A-Z0-9_]+$') -or ($Name -cmatch '^[A-Z0-9_]+\.[A-Z0-9_]+$')
}
function New-ShortFatName([string]$LongName, $Used) {
$upper = $LongName.ToUpperInvariant()
if ((Test-ShortFatName $upper) -and -not $Used.ContainsKey($upper)) {
$Used[$upper] = $true
$parts = $upper.Split('.')
$base = $parts[0].PadRight(8, ' ')
$ext = ''
if ($parts.Count -eq 2) { $ext = $parts[1] }
return ($base + $ext.PadRight(3, ' '))
}
$dot = $upper.LastIndexOf('.')
if ($dot -ge 0) {
$baseRaw = $upper.Substring(0, $dot)
$extRaw = $upper.Substring($dot + 1)
} else {
$baseRaw = $upper
$extRaw = ''
}
$baseClean = ($baseRaw -replace '[^A-Z0-9]', '')
$extClean = ($extRaw -replace '[^A-Z0-9]', '')
if ($baseClean.Length -eq 0) { $baseClean = 'FILE' }
if ($extClean.Length -gt 3) { $extClean = $extClean.Substring(0, 3) }
for ($i = 1; $i -lt 1000; $i++) {
$suffix = "~$i"
$take = [Math]::Min(8 - $suffix.Length, $baseClean.Length)
$candidateBase = $baseClean.Substring(0, $take) + $suffix
$display = $candidateBase
if ($extClean.Length -gt 0) { $display = "$candidateBase.$extClean" }
if (-not $Used.ContainsKey($display)) {
$Used[$display] = $true
return ($candidateBase.PadRight(8, ' ') + $extClean.PadRight(3, ' '))
}
}
throw "Unable to generate FAT short name for $LongName"
}
function Get-ShortNameBytes([string]$ShortName) {
return [System.Text.Encoding]::ASCII.GetBytes($ShortName)
}
function Get-LfnChecksum([byte[]]$ShortBytes) {
$sum = 0
foreach ($b in $ShortBytes) {
$sum = (((($sum -band 1) * 0x80) + ($sum -shr 1) + $b) -band 0xff)
}
return [byte]$sum
}
function New-LfnEntry([string]$Chunk, [int]$Order, [byte]$Checksum) {
$entry = [byte[]]::new(32)
for ($i = 0; $i -lt 32; $i++) { $entry[$i] = 0xff }
$entry[0] = [byte]$Order
$entry[11] = 0x0f
$entry[12] = 0x00
$entry[13] = $Checksum
$entry[26] = 0x00
$entry[27] = 0x00
$positions = @(1,3,5,7,9,14,16,18,20,22,24,28,30)
$chars = $Chunk.ToCharArray()
for ($i = 0; $i -lt $positions.Count; $i++) {
$pos = $positions[$i]
if ($i -lt $chars.Length) {
$code = [int][char]$chars[$i]
$entry[$pos] = [byte]($code -band 0xff)
$entry[$pos + 1] = [byte](($code -shr 8) -band 0xff)
} elseif ($i -eq $chars.Length) {
$entry[$pos] = 0x00
$entry[$pos + 1] = 0x00
}
}
return $entry
}
function New-ShortEntry([string]$ShortName, [byte]$Attr, [int]$Cluster, [uint32]$Size) {
$entry = [byte[]]::new(32)
$nameBytes = Get-ShortNameBytes $ShortName
[Array]::Copy($nameBytes, 0, $entry, 0, 11)
$entry[11] = $Attr
Set-Le16 $entry 20 (($Cluster -shr 16) -band 0xffff)
Set-Le16 $entry 26 ($Cluster -band 0xffff)
Set-Le32 $entry 28 $Size
return $entry
}
function Add-DirectoryEntry($Entries, [string]$Name, [byte]$Attr, [int]$Cluster, [uint32]$Size, $UsedNames) {
$shortName = New-ShortFatName $Name $UsedNames
$shortBytes = Get-ShortNameBytes $shortName
2026-08-29 18:47:54 +08:00
# 用原始名(含大小写)判断是否生成 LFN:Test-ShortFatName 内部用
# -cmatch '[a-z]' 检测小写字母,含小写的名字(如 limine)必须生成
# LFN 才能在 Windows 上保留原始大小写显示。FAT32 查找仍不区分大小写。
# 注意:拆成两步赋值——PowerShell 5.1 对 `-not (函数调用) -or (...)` 内联
# 表达式解析异常返回 $null,分开赋值保证布尔短路正常。
$isUpperShort = Test-ShortFatName $Name
$needsLfn = (-not $isUpperShort) -or (($shortName.Trim() -replace ' +', '') -ne ($Name.ToUpperInvariant() -replace '\.', ''))
if ($needsLfn) {
$checksum = Get-LfnChecksum $shortBytes
$chunks = @()
for ($i = 0; $i -lt $Name.Length; $i += 13) {
$len = [Math]::Min(13, $Name.Length - $i)
$chunks += $Name.Substring($i, $len)
}
for ($i = $chunks.Count; $i -ge 1; $i--) {
$order = $i
if ($i -eq $chunks.Count) { $order = $order -bor 0x40 }
[void]$Entries.Add((New-LfnEntry $chunks[$i - 1] $order $checksum))
}
}
[void]$Entries.Add((New-ShortEntry $shortName $Attr $Cluster $Size))
}
function Get-GuidBytes([string]$GuidString) {
return ([Guid]$GuidString).ToByteArray()
}
function New-GptHeader([UInt64]$CurrentLba, [UInt64]$BackupLba, [UInt64]$FirstUsable, [UInt64]$LastUsable, [byte[]]$DiskGuid, [UInt64]$EntriesLba, [uint32]$EntryCount, [uint32]$EntrySize, [uint32]$EntriesCrc) {
$header = [byte[]]::new(512)
[Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('EFI PART'), 0, $header, 0, 8)
Set-Le32 $header 8 0x00010000
Set-Le32 $header 12 92
Set-Le64 $header 24 $CurrentLba
Set-Le64 $header 32 $BackupLba
Set-Le64 $header 40 $FirstUsable
Set-Le64 $header 48 $LastUsable
[Array]::Copy($DiskGuid, 0, $header, 56, 16)
Set-Le64 $header 72 $EntriesLba
Set-Le32 $header 80 $EntryCount
Set-Le32 $header 84 $EntrySize
Set-Le32 $header 88 $EntriesCrc
$crcData = [byte[]]::new(92)
[Array]::Copy($header, 0, $crcData, 0, 92)
Set-Le32 $crcData 16 0
$crc = Get-Crc32 $crcData
Set-Le32 $header 16 $crc
return $header
}
# Legacy: whole SYSTEM/ on a single FAT32 ESP. Prefer New-GptFat32Ext4Image.
function New-GptFat32Image([string]$SourceDir, [string]$ImagePath, [int]$SizeMB = 128) {
$bytesPerSector = 512
$totalSectors = [UInt64](($SizeMB * 1024 * 1024) / $bytesPerSector)
$lastLba = $totalSectors - 1
$entryCount = [uint32]128
$entrySize = [uint32]128
$entrySectors = [UInt64](($entryCount * $entrySize) / $bytesPerSector)
$firstUsable = [UInt64](2 + $entrySectors)
$lastUsable = [UInt64]($lastLba - $entrySectors - 1)
$partStart = [UInt64]2048
$partEnd = $lastUsable
$partSectors = [UInt64]($partEnd - $partStart + 1)
$partitionOffset = [Int64]($partStart * $bytesPerSector)
# 4KB clusters (8 sectors) on the 2GB default image; 32KB (64 sectors)
# when SYSTEM/ is large enough that 65525 clusters still fit.
# 32KB on a 2GB volume would drop below the FAT32 minimum cluster count.
2026-08-09 22:14:01 +08:00
$sectorsPerCluster = 8
if ($SizeMB -ge 4096) { $sectorsPerCluster = 64 }
$reservedSectors = 32
$numFats = 2
$fatSectors = 1
do {
$dataSectors = [Int64]$partSectors - $reservedSectors - ($numFats * $fatSectors)
$clusterCount = [int]($dataSectors / $sectorsPerCluster)
$neededFatSectors = [int]((($clusterCount + 2) * 4 + $bytesPerSector - 1) / $bytesPerSector)
if ($neededFatSectors -eq $fatSectors) { break }
$fatSectors = $neededFatSectors
} while ($true)
if ($clusterCount -lt 65525) {
throw "Invalid FAT32 cluster count: $clusterCount"
}
$fatOffset = [Int64]($partitionOffset + $reservedSectors * $bytesPerSector)
$dataOffset = [Int64]($partitionOffset + ($reservedSectors + $numFats * $fatSectors) * $bytesPerSector)
$clusterSize = $sectorsPerCluster * $bytesPerSector
$fat = [UInt32[]]::new($clusterCount + 2)
$fat[0] = 0x0ffffff8
$fat[1] = 0x0fffffff
$fat[2] = 0x0fffffff
$script:fatNextCluster = 3
function Allocate-Chain([Int64]$ByteCount) {
$needed = [Math]::Max(1, [int](($ByteCount + $clusterSize - 1) / $clusterSize))
$first = $script:fatNextCluster
for ($i = 0; $i -lt $needed; $i++) {
$cluster = $script:fatNextCluster
$script:fatNextCluster++
if ($script:fatNextCluster -ge $fat.Length) { throw 'FAT32 image is too small' }
if ($i -eq $needed - 1) {
$fat[$cluster] = 0x0fffffff
} else {
$fat[$cluster] = [UInt32]($cluster + 1)
}
}
return $first
}
function Get-ClusterOffset([int]$Cluster) {
return [Int64]($dataOffset + (($Cluster - 2) * $clusterSize))
}
function Write-FileContent($Stream, [string]$Path, [int]$FirstCluster) {
$input = [System.IO.File]::OpenRead($Path)
try {
$buf = [byte[]]::new($clusterSize)
$cluster = $FirstCluster
$remaining = $input.Length
while ($remaining -gt 0) {
$chunk = [int][Math]::Min([int64]$clusterSize, [int64]$remaining)
$n = $input.Read($buf, 0, $chunk)
if ($n -le 0) { break }
[void]$Stream.Seek((Get-ClusterOffset $cluster), [System.IO.SeekOrigin]::Begin)
$Stream.Write($buf, 0, $n)
$remaining -= $n
if ($remaining -gt 0) { $cluster = $fat[$cluster] }
}
} finally {
$input.Close()
}
}
function Write-Directory($Stream, [string]$Path, [int]$SelfCluster, [int]$ParentCluster, [bool]$IsRoot) {
$entries = [System.Collections.ArrayList]::new()
$used = @{}
if (-not $IsRoot) {
[void]$entries.Add((New-ShortEntry '. ' 0x10 $SelfCluster 0))
[void]$entries.Add((New-ShortEntry '.. ' 0x10 $ParentCluster 0))
}
$items = Get-ChildItem -LiteralPath $Path -Force | Where-Object { $_.Name -notlike '*.tmp*' } | Sort-Object @{Expression={$_.PSIsContainer};Descending=$true}, Name
2026-08-09 22:14:01 +08:00
# 两遍策略:先收集所有目录项(含 LFN),同时为子目录/文件分配簇链。
# 子目录需要先分配簇,但目录项总数此时未知——先收集 entries,
# 再根据 entries 总字节数为本目录分配足够簇,最后回写 FAT 链。
# 注意:SelfCluster 已由调用方分配(根目录固定 cluster 2,子目录在调用前分配)。
# 对于子目录,调用方只分配了 1 个簇——如果目录项超过 1 簇,需要扩展链。
# 解决方案:先收集 entries + 分配子项簇,然后检查本目录需要的簇数,
# 如果超过已分配的,追加簇并更新 FAT 链。
foreach ($item in $items) {
if ($item.PSIsContainer) {
$childCluster = Allocate-Chain $clusterSize
Add-DirectoryEntry $entries $item.Name 0x10 $childCluster 0 $used
Write-Directory $Stream $item.FullName $childCluster $SelfCluster $false
} else {
if ($item.Length -ge 4GB) {
throw "FAT32 cannot store $($item.FullName) ($([int]($item.Length / 1MB))MB). Keep SYSTEM files under 4GiB."
}
$fileSize = [uint32]$item.Length
$fileCluster = Allocate-Chain $item.Length
Add-DirectoryEntry $entries $item.Name 0x20 $fileCluster $fileSize $used
Write-FileContent $Stream $item.FullName $fileCluster
}
}
2026-08-09 22:14:01 +08:00
# 计算本目录需要的簇数(目录项可能跨多簇)
$entryBytes = $entries.Count * 32
2026-08-09 22:14:01 +08:00
$neededClusters = [Math]::Max(1, [int](($entryBytes + $clusterSize - 1) / $clusterSize))
$neededBytes = $neededClusters * $clusterSize
# 如果本目录需要多簇,扩展 FAT 链(SelfCluster 是首簇)
if ($neededClusters -gt 1) {
$cur = $SelfCluster
for ($i = 1; $i -lt $neededClusters; $i++) {
$next = $script:fatNextCluster
$script:fatNextCluster++
if ($script:fatNextCluster -ge $fat.Length) { throw 'FAT32 image is too small' }
$fat[$cur] = [UInt32]$next
$cur = $next
}
$fat[$cur] = 0x0fffffff
}
$dirBytes = [byte[]]::new($neededBytes)
$offset = 0
foreach ($entry in $entries) {
[Array]::Copy($entry, 0, $dirBytes, $offset, 32)
$offset += 32
}
2026-08-09 22:14:01 +08:00
# 写入所有目录簇(按 FAT 链顺序)
$writeCluster = $SelfCluster
$bytesWritten = 0
while ($bytesWritten -lt $neededBytes) {
$chunk = [Math]::Min($clusterSize, $neededBytes - $bytesWritten)
$chunkBytes = [byte[]]::new($clusterSize)
[Array]::Copy($dirBytes, $bytesWritten, $chunkBytes, 0, $chunk)
Write-At $Stream (Get-ClusterOffset $writeCluster) $chunkBytes
$bytesWritten += $clusterSize
if ($bytesWritten -lt $neededBytes) {
$writeCluster = $fat[$writeCluster]
}
}
}
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $ImagePath) | Out-Null
$fs = [System.IO.File]::Open($ImagePath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
try {
$fs.SetLength($SizeMB * 1024 * 1024)
$mbr = [byte[]]::new(512)
$mbr[446 + 4] = 0xee
Set-Le32 $mbr (446 + 8) 1
$protectiveSectors = [UInt64]($totalSectors - 1)
if ($protectiveSectors -gt [UInt64]4294967295) { $protectiveSectors = [UInt64]4294967295 }
Set-Le32 $mbr (446 + 12) $protectiveSectors
$mbr[510] = 0x55
$mbr[511] = 0xaa
Write-At $fs 0 $mbr
$partitionEntries = [byte[]]::new($entryCount * $entrySize)
$espType = Get-GuidBytes 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b'
$partGuid = ([Guid]::NewGuid()).ToByteArray()
[Array]::Copy($espType, 0, $partitionEntries, 0, 16)
[Array]::Copy($partGuid, 0, $partitionEntries, 16, 16)
Set-Le64 $partitionEntries 32 $partStart
Set-Le64 $partitionEntries 40 $partEnd
$nameBytes = [System.Text.Encoding]::Unicode.GetBytes('Deshab ESP')
[Array]::Copy($nameBytes, 0, $partitionEntries, 56, $nameBytes.Length)
$entriesCrc = Get-Crc32 $partitionEntries
$diskGuid = ([Guid]::NewGuid()).ToByteArray()
$primaryHeader = New-GptHeader 1 $lastLba $firstUsable $lastUsable $diskGuid 2 $entryCount $entrySize $entriesCrc
$backupEntriesLba = [UInt64]($lastLba - $entrySectors)
$backupHeader = New-GptHeader $lastLba 1 $firstUsable $lastUsable $diskGuid $backupEntriesLba $entryCount $entrySize $entriesCrc
Write-At $fs ($bytesPerSector * 1) $primaryHeader
Write-At $fs ($bytesPerSector * 2) $partitionEntries
Write-At $fs ([Int64]($backupEntriesLba * $bytesPerSector)) $partitionEntries
Write-At $fs ([Int64]($lastLba * $bytesPerSector)) $backupHeader
$boot = [byte[]]::new(512)
$boot[0] = 0xeb; $boot[1] = 0x58; $boot[2] = 0x90
[Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('DESHAB '), 0, $boot, 3, 8)
Set-Le16 $boot 11 $bytesPerSector
$boot[13] = [byte]$sectorsPerCluster
Set-Le16 $boot 14 $reservedSectors
$boot[16] = [byte]$numFats
Set-Le16 $boot 17 0
Set-Le16 $boot 19 0
$boot[21] = 0xf8
Set-Le16 $boot 22 0
Set-Le16 $boot 24 63
Set-Le16 $boot 26 255
Set-Le32 $boot 28 $partStart
Set-Le32 $boot 32 $partSectors
Set-Le32 $boot 36 $fatSectors
Set-Le16 $boot 40 0
Set-Le16 $boot 42 0
Set-Le32 $boot 44 2
Set-Le16 $boot 48 1
Set-Le16 $boot 50 6
$boot[64] = 0x80
$boot[66] = 0x29
Set-Le32 $boot 67 0x44534842
[Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('DESHAB '), 0, $boot, 71, 11)
[Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('FAT32 '), 0, $boot, 82, 8)
$boot[510] = 0x55; $boot[511] = 0xaa
Write-At $fs $partitionOffset $boot
Write-At $fs ($partitionOffset + 6 * $bytesPerSector) $boot
$fsinfo = [byte[]]::new(512)
Set-Le32 $fsinfo 0 0x41615252
Set-Le32 $fsinfo 484 0x61417272
Set-Le32 $fsinfo 488 4294967295
Set-Le32 $fsinfo 492 3
$fsinfo[508] = 0x00; $fsinfo[509] = 0x00; $fsinfo[510] = 0x55; $fsinfo[511] = 0xaa
Write-At $fs ($partitionOffset + $bytesPerSector) $fsinfo
Write-At $fs ($partitionOffset + 7 * $bytesPerSector) $fsinfo
Write-Directory $fs $SourceDir 2 2 $true
$fatBytes = [byte[]]::new($fatSectors * $bytesPerSector)
for ($i = 0; $i -lt $fat.Length; $i++) {
$off = $i * 4
if ($off + 3 -ge $fatBytes.Length) { break }
Set-Le32 $fatBytes $off $fat[$i]
}
Write-At $fs $fatOffset $fatBytes
Write-At $fs ($fatOffset + $fatSectors * $bytesPerSector) $fatBytes
}
finally {
$fs.Close()
}
Write-Host "[build] GPT + FAT32 ESP IMG: $ImagePath"
}
2026-08-29 18:47:54 +08:00
# Convert a raw GPT disk image to a fixed VHD by appending the 512-byte VHD
# footer. Fixed VHD = raw data + footer, so this is a cheap copy + footer.
# Windows can mount the result natively (double-click / Mount-DiskImage).
function ConvertTo-FixedVhd([string]$RawImage, [string]$VhdPath) {
if (-not (Test-Path $RawImage)) { throw "Raw image not found: $RawImage" }
Copy-Item -LiteralPath $RawImage -Destination $VhdPath -Force
$fs = [System.IO.File]::Open($VhdPath, [System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
try {
$size = $fs.Length
$totalSectors = [UInt64]($size / 512)
$footer = [byte[]]::new(512)
[Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('conectix'), 0, $footer, 0, 8)
Set-Le32 $footer 8 0x00000002 # features
Set-Le32 $footer 12 0x00010000 # file format version
Set-Le64 $footer 16 ([UInt64]::MaxValue) # data offset: fixed disk
$epoch = [DateTime]::new(2000, 1, 1, 0, 0, 0, [DateTimeKind]::Utc)
Set-Le32 $footer 24 ([UInt64][Math]::Max(0, [int]([DateTime]::UtcNow - $epoch).TotalSeconds))
[Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('dshb'), 0, $footer, 28, 4)
Set-Le32 $footer 32 0x00010000 # creator version
[Array]::Copy([System.Text.Encoding]::ASCII.GetBytes('Wi2k'), 0, $footer, 36, 4)
Set-Le64 $footer 40 ([UInt64]$size) # original size
Set-Le64 $footer 48 ([UInt64]$size) # current size
# Disk geometry (VHD layout is big-endian here: 2-byte cylinders + 1 head + 1 spt)
$spt = 0; $h = 0
if ($totalSectors -ge 65535 * 16 * 255) { $spt = 255; $h = 16 }
elseif ($totalSectors -ge 65535 * 16 * 17) { $spt = 63; $h = 16 }
else { $spt = 17; $h = 16 }
$c = [UInt64][Math]::Floor([double]$totalSectors / ($h * $spt))
if ($c -gt 65535) { $c = 65535 }
$footer[56] = [byte](($c -shr 8) -band 0xFF)
$footer[57] = [byte]($c -band 0xFF)
$footer[58] = [byte]$h
$footer[59] = [byte]$spt
Set-Le32 $footer 60 2 # disk type: fixed
[Array]::Copy(([Guid]::NewGuid()).ToByteArray(), 0, $footer, 68, 16)
$footer[84] = 0 # saved state: normal
# Checksum: one's complement of the uint32 sum over the whole footer
$sum = [UInt64]0
for ($i = 0; $i -lt 512; $i += 4) {
$sum = ($sum + [BitConverter]::ToUInt32($footer, $i)) -band 0xFFFFFFFF
}
Set-Le32 $footer 64 ((0xFFFFFFFF -bxor $sum) -band 0xFFFFFFFF)
[void]$fs.Seek(0, [System.IO.SeekOrigin]::End)
$fs.Write($footer, 0, 512)
} finally {
$fs.Close()
}
Write-Host "[build] VHD (Windows mountable): $VhdPath"
}
2026-08-09 22:14:01 +08:00
# Build a single image variant (dev/release): inject FUCK + firstInit.txt from
# build/configs/<variant>/ into SYSTEM/, then package via New-GptFat32Ext4Image.
2026-08-09 22:14:01 +08:00
# If $ImagePath is $null, only injects config without packaging (used to
# restore SYSTEM dir to dev config after dual-image build).
2026-08-29 18:47:54 +08:00
# NOTE: configs/<variant> dir may be absent; then SYSTEM is packaged as-is.
2026-08-09 22:14:01 +08:00
function Build-ImageVariant([string]$VariantName, [string]$ImagePath) {
$cfgDir = Join-Path $ConfigsDir $VariantName
2026-08-29 18:47:54 +08:00
if (Test-Path $cfgDir) {
$fuckSrc = Join-Path $cfgDir 'FUCK'
$fuckDst = Join-Path $SystemDir 'system\deshab64\FUCK'
$fiSrc = Join-Path $cfgDir 'firstInit.txt'
$fiDst = Join-Path $SystemDir 'system\user\use\firstInit.txt'
2026-08-09 22:14:01 +08:00
2026-08-29 18:47:54 +08:00
if (-not (Test-Path $fuckSrc)) { throw "Missing FUCK template: $fuckSrc" }
if (-not (Test-Path $fiSrc)) { throw "Missing firstInit.txt template: $fiSrc" }
2026-08-09 22:14:01 +08:00
2026-08-29 18:47:54 +08:00
Copy-Item -LiteralPath $fuckSrc -Destination $fuckDst -Force
Copy-Item -LiteralPath $fiSrc -Destination $fiDst -Force
Write-Host "[build] Injected config variant=$VariantName (FUCK + firstInit.txt)"
} else {
Write-Host "[build] WARN: configs/$VariantName absent, packaging SYSTEM as-is"
}
2026-08-09 22:14:01 +08:00
if ($ImagePath) {
Write-Host '[build] Packaging SYSTEM/ as GPT: p1 FAT32 ESP (Limine) + p2 ext4 data'
New-GptFat32Ext4Image $SystemDir $ImagePath
2026-08-29 18:47:54 +08:00
if ($Vhd) {
$vhdPath = [System.IO.Path]::ChangeExtension($ImagePath, '.vhd')
ConvertTo-FixedVhd $ImagePath $vhdPath
}
2026-08-09 22:14:01 +08:00
}
}
Write-Host '[build] Building UTSM kernel...'
2026-08-09 22:14:01 +08:00
# VSCode integration Phase 0 hook: detect optional persistent rw volume for
# Linux guest. The actual virtio-blk backend wiring is added in Phase 4.
if (Test-Path $ExtraRootfsPath) {
Write-Host "[build] NOTE: linux-extra-rootfs.img present at $ExtraRootfsPath"
Write-Host '[build] (Phase 4 will wire it as /dev/vdc for overlayfs upperdir)'
}
$clang = Find-Tool 'clang'
$lld = Find-Tool 'ld.lld'
$make = Find-Tool 'make' @('mingw32-make')
if (-not $clang) {
throw 'clang was not found. Install LLVM/Clang or provide a freestanding x86_64 ELF toolchain.'
}
if (-not $lld) {
throw 'ld.lld was not found. Install LLVM lld or provide a compatible linker.'
}
if (-not $make) {
throw 'make was not found. Install make.'
}
Write-Host "[build] clang: $clang"
Write-Host "[build] ld.lld: $lld"
Write-Host "[build] make: $make"
New-Item -ItemType Directory -Force -Path $BuildTmp | Out-Null
$env:TMP = $BuildTmp
$env:TEMP = $BuildTmp
2026-08-09 22:14:01 +08:00
# Ensure make can find sh.exe + Unix tools (mkdir, etc.).
# MAKEFILEs use `mkdir -p` which cmd.exe cannot run; mingw32-make needs sh.exe
# in PATH. Git for Windows' usr/bin provides a complete sh + coreutils set.
foreach ($shDir in @('C:\Program Files\Git\usr\bin', 'C:\msys64\usr\bin')) {
if ((Test-Path (Join-Path $shDir 'sh.exe')) -and ($env:PATH -notlike "*$shDir*")) {
$env:PATH = "$shDir;$env:PATH"
Write-Host "[build] Added Unix tools dir to PATH: $shDir"
break
}
}
Push-Location $UtsmDir
try {
& $make -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make UTSM failed with exit code $LASTEXITCODE"
}
}
finally {
Pop-Location
}
Write-Host '[build] Building DSK main kernel...'
2026-07-31 13:53:49 +08:00
# Generate logo_data.c from Logo.png (RGBA8888 embedded data)
$LogoPng = Join-Path $Root 'Logo.png'
$LogoDataC = Join-Path $DskDir 'logo_data.c'
$PngToRgba = Join-Path $DskDir 'png_to_rgba.py'
if (Test-Path $LogoPng) {
# Find Python
$Python = $null
foreach ($p in @('C:\Users\林濬哲\AppData\Local\Programs\Python\Python312\python.exe',
'C:\msys64\mingw64\bin\python.exe',
'python', 'python3')) {
try { $cmd = Get-Command $p -ErrorAction Stop; $Python = $cmd.Source; break } catch {}
}
if ($Python) {
# Regenerate if Logo.png is newer than logo_data.c
if (-not (Test-Path $LogoDataC) -or ((Get-Item $LogoPng).LastWriteTime -gt (Get-Item $LogoDataC).LastWriteTime)) {
Write-Host '[build] Generating logo_data.c from Logo.png...'
& $Python $PngToRgba $LogoPng $LogoDataC
if ($LASTEXITCODE -ne 0) { Write-Host '[build] WARNING: logo_data.c generation failed, using existing file' }
}
} else {
Write-Host '[build] WARNING: Python not found, cannot regenerate logo_data.c'
}
} else {
Write-Host '[build] WARNING: Logo.png not found'
}
# Generate panic_logo_data.inc from ohMyLogo.png (panic screen logo + bg color)
$PanicLogoPng = Join-Path $Root 'ohMyLogo.png'
$PanicLogoInc = Join-Path $UtsmDir 'kernel\panic_logo_data.inc'
$PanicGenPy = Join-Path $UtsmDir 'kernel\gen_panic_logo.py'
if (Test-Path $PanicLogoPng) {
$Python = $null
foreach ($p in @('C:\Users\林濬哲\AppData\Local\Programs\Python\Python312\python.exe',
'C:\msys64\mingw64\bin\python.exe',
'python', 'python3')) {
try { $cmd = Get-Command $p -ErrorAction Stop; $Python = $cmd.Source; break } catch {}
}
if ($Python) {
if (-not (Test-Path $PanicLogoInc) -or ((Get-Item $PanicLogoPng).LastWriteTime -gt (Get-Item $PanicLogoInc).LastWriteTime)) {
Write-Host '[build] Generating panic_logo_data.inc from ohMyLogo.png...'
& $Python $PanicGenPy $PanicLogoPng $PanicLogoInc
if ($LASTEXITCODE -ne 0) { Write-Host '[build] WARNING: panic_logo_data.inc generation failed, using existing file' }
}
} else {
Write-Host '[build] WARNING: Python not found, cannot regenerate panic_logo_data.inc'
}
} else {
Write-Host '[build] WARNING: ohMyLogo.png not found (panic screen keeps embedded data)'
}
# Generate desktop textures (PNG/webP -> raw RGBA for the desktop material pack)
$GenTex = Join-Path $Root 'CODE\desktop\gen_textures.py'
if (Test-Path $GenTex) {
$TexSrc = Join-Path $Root 'SYSTEM\system\deshab64\desktop\textures\startMenuLogo.png'
$TexStamp = Join-Path $Root 'SYSTEM\system\deshab64\desktop\textures\startMenuLogo.rgba'
$needTex = -not (Test-Path $TexStamp)
if ($needTex -and (Test-Path $TexSrc) -and (Test-Path $TexStamp)) {
$needTex = (Get-Item $TexSrc).LastWriteTime -gt (Get-Item $TexStamp).LastWriteTime
}
if ($needTex) {
Write-Host '[build] Generating desktop textures (gen_textures.py)...'
$texDone = $false
$Python = $null
foreach ($p in @('C:\Users\林濬哲\AppData\Local\Programs\Python\Python312\python.exe',
'C:\msys64\mingw64\bin\python.exe',
'python', 'python3')) {
try { $cmd = Get-Command $p -ErrorAction Stop; $Python = $cmd.Source; break } catch {}
}
if ($Python) {
& $Python $GenTex
if ($LASTEXITCODE -eq 0) { $texDone = $true }
}
if (-not $texDone) {
# Fallback: WSL python3 (has Pillow on this machine)
$wslPath = ($GenTex -replace '^([A-Za-z]):', '/mnt/$1').ToLower()
& wsl python3 $wslPath
if ($LASTEXITCODE -eq 0) { $texDone = $true }
}
if (-not $texDone) {
Write-Host '[build] WARNING: desktop texture generation failed (material pack may be incomplete)'
}
}
}
try {
& $make -C "$DskDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make DSK failed with exit code $LASTEXITCODE"
}
}
finally {
}
if (-not (Test-Path $Output)) {
throw "UTSM build did not produce $Output"
}
if (-not (Test-Path $DskOutput)) {
throw "DSK build did not produce $DskOutput"
}
$FirstInitDir = Join-Path $Root 'CODE\firstInit'
$FirstInitOutput = Join-Path $SystemDir 'system\user\use\FirstInit.elf'
Write-Host '[build] Building FirstInit.elf...'
try {
& $make -C "$FirstInitDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make FirstInit failed with exit code $LASTEXITCODE"
}
}
finally {
}
if (-not (Test-Path $FirstInitOutput)) {
throw "FirstInit build did not produce $FirstInitOutput"
}
Write-Host "[build] FirstInit Output: $FirstInitOutput"
$LoginDir = Join-Path $Root 'CODE\login'
$LoginOutput = Join-Path $SystemDir 'system\user\use\login.elf'
Write-Host '[build] Building login.elf...'
try {
& $make -C "$LoginDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make login failed with exit code $LASTEXITCODE"
}
}
finally {
}
if (-not (Test-Path $LoginOutput)) {
throw "login build did not produce $LoginOutput"
}
Write-Host "[build] login Output: $LoginOutput"
$MouseDir = Join-Path $Root 'CODE\mouse'
$MouseOutput = Join-Path $SystemDir 'system\deshab64\mouse\mouseInit.elf'
Write-Host '[build] Building mouseInit.elf...'
try {
& $make -C "$MouseDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make mouseInit failed with exit code $LASTEXITCODE"
}
}
finally {
}
if (-not (Test-Path $MouseOutput)) {
throw "mouseInit build did not produce $MouseOutput"
}
Write-Host "[build] mouseInit Output: $MouseOutput"
$NetmanDir = Join-Path $Root 'CODE\netman'
$NetmanOutput = Join-Path $SystemDir 'system\deshab64\network\netman.elf'
Write-Host '[build] Building netman.elf...'
try {
& $make -C "$NetmanDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make netman failed with exit code $LASTEXITCODE"
}
}
finally {
}
if (-not (Test-Path $NetmanOutput)) {
throw "netman build did not produce $NetmanOutput"
}
Write-Host "[build] netman Output: $NetmanOutput"
$ShellDir = Join-Path $Root 'CODE\shell'
$ShellOutput = Join-Path $SystemDir 'system\deshab64\shell.elf'
Write-Host '[build] Building shell.elf...'
try {
& $make -C "$ShellDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make shell failed with exit code $LASTEXITCODE"
}
}
finally {
}
if (-not (Test-Path $ShellOutput)) {
throw "shell build did not produce $ShellOutput"
}
Write-Host "[build] shell Output: $ShellOutput"
$DesktopDir = Join-Path $Root 'CODE\desktop'
$DesktopOutput = Join-Path $SystemDir 'system\deshab64\desktop.elf'
Write-Host '[build] Building desktop.elf...'
try {
& $make -C "$DesktopDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make desktop failed with exit code $LASTEXITCODE"
}
}
finally {
}
if (-not (Test-Path $DesktopOutput)) {
throw "desktop build did not produce $DesktopOutput"
}
Write-Host "[build] desktop Output: $DesktopOutput"
2026-07-28 14:35:14 +08:00
# ---------- CMD (Windows-style command line + PE/EXE compat) ----------
$CmdDir = Join-Path $Root 'CODE\cmd'
$CmdOutput = Join-Path $SystemDir 'system\deshab64\cmd.elf'
Write-Host '[build] Building cmd.elf...'
try {
& $make -C "$CmdDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
throw "make cmd failed with exit code $LASTEXITCODE"
}
}
finally {
}
if (-not (Test-Path $CmdOutput)) {
throw "cmd build did not produce $CmdOutput"
}
Write-Host "[build] cmd Output: $CmdOutput"
# ---------- Tool applications ----------
$ToolsDir = Join-Path $Root 'CODE\tools'
$ToolsOutDir = Join-Path $SystemDir 'system\deshab64\tools'
2026-08-09 22:14:01 +08:00
$toolApps = @('editor', 'fileman', 'browser', 'curl', 'ping', 'settings')
foreach ($tool in $toolApps) {
$toolDir = Join-Path $ToolsDir $tool
$toolMake = Join-Path $toolDir 'MAKEFILE'
if (Test-Path $toolMake) {
Write-Host "[build] Building ${tool}.elf..."
try {
& $make -C "$toolDir" -f MAKEFILE "CC=$clang" "LD=$lld"
if ($LASTEXITCODE -ne 0) {
Write-Host "[build] WARNING: make $tool failed with exit code $LASTEXITCODE"
}
}
finally {
}
}
}
Write-Host '[build] Building DKM framebuffer driver...'
Build-DkmDriver (Join-Path $Root 'CODE\DKM\console_fb\console_fb.c') (Join-Path $SystemDir 'driver\console\console_fb.drv')
# stage0: platform + bus — timer 提供 PIT 校准,acpi 枚举 RSDP/XSDT,pci 扫描配置空间。
# 这三个驱动 manifest.json 里是必需的;055c521 把它们从 git 移除后此处必须重新编译,
# 否则 limine 会 panic「Failed to open module boot():/driver/platform/timer.drv」。
Write-Host '[build] Building DKM platform/bus drivers...'
Build-DkmDriver (Join-Path $Root 'CODE\DKM\timer\timer.c') (Join-Path $SystemDir 'driver\platform\timer.drv')
Build-DkmDriver (Join-Path $Root 'CODE\DKM\acpi\acpi.c') (Join-Path $SystemDir 'driver\platform\acpi.drv')
Build-DkmDriver (Join-Path $Root 'CODE\DKM\pci\pci.c') (Join-Path $SystemDir 'driver\bus\pci.drv')
# B7: apic.drv 纳入构建 (原 platform/apic.drv 为预置二进制, 不随 apic.c 更新)
Write-Host '[build] Building DKM platform apic driver...'
Build-DkmDriver (Join-Path $Root 'CODE\DKM\apic\apic.c') (Join-Path $SystemDir 'driver\platform\apic.drv')
2026-07-31 13:53:49 +08:00
Write-Host '[build] Building DKM storage drivers...'
Build-DkmDriver (Join-Path $Root 'CODE\DKM\ahci\ahci.c') (Join-Path $SystemDir 'driver\block\ahci.drv')
2026-07-31 13:53:49 +08:00
Build-DkmDriver (Join-Path $Root 'CODE\DKM\nvme\nvme.c') (Join-Path $SystemDir 'driver\block\nvme.drv')
2026-08-29 18:47:54 +08:00
Build-DkmDriver (Join-Path $Root 'CODE\DKM\ehci\ehci.c') (Join-Path $SystemDir 'driver\block\ehci.drv')
2026-07-31 13:53:49 +08:00
Build-DkmDriver (Join-Path $Root 'CODE\DKM\xhci\xhci.c') (Join-Path $SystemDir 'driver\block\xhci.drv')
# stage2: filesystem — bootfs 挂 boot module,vfs 汇总,fat32 只读 FAT32,devfs 暴露 /dev。
Write-Host '[build] Building DKM filesystem drivers...'
Build-DkmDriver (Join-Path $Root 'CODE\DKM\bootfs\bootfs.c') (Join-Path $SystemDir 'driver\fs\bootfs.drv')
Build-DkmDriver (Join-Path $Root 'CODE\DKM\vfs\vfs.c') (Join-Path $SystemDir 'driver\fs\vfs.drv')
Build-DkmDriver (Join-Path $Root 'CODE\DKM\fat32\fat32.c') (Join-Path $SystemDir 'driver\fs\fat32.drv')
Build-DkmDriver (Join-Path $Root 'CODE\DKM\devfs\devfs.c') (Join-Path $SystemDir 'driver\fs\devfs.drv')
Write-Host '[build] Building DKM network drivers...'
Build-DkmDriver (Join-Path $Root 'CODE\DKM\e1000\e1000.c') (Join-Path $SystemDir 'driver\net\e1000.drv')
Build-DkmDriver (Join-Path $Root 'CODE\DKM\virtio_net\virtio_net.c') (Join-Path $SystemDir 'driver\net\virtio_net.drv')
Build-DkmDriver (Join-Path $Root 'CODE\DKM\ath9k\ath9k.c') (Join-Path $SystemDir 'driver\net\ath9k.drv')
# stage3: input — ps2kbd 注册 IRQ1。
Write-Host '[build] Building DKM input drivers...'
Build-DkmDriver (Join-Path $Root 'CODE\DKM\ps2kbd\ps2kbd.c') (Join-Path $SystemDir 'driver\input\ps2kbd.drv')
Write-Host "[build] Output: $Output"
Write-Host "[build] DSK Output: $DskOutput"
2026-08-09 22:14:01 +08:00
Write-Host "[build] Variant: $Variant"
# Remove legacy single-image to avoid confusion
$LegacyImg = Join-Path $IsoDir 'deshab.img'
if (Test-Path $LegacyImg) {
Remove-Item -LiteralPath $LegacyImg -Force
Write-Host "[build] Removed legacy image: $LegacyImg"
}
Write-Host '[build] Packaging SYSTEM to GPT images (p1 FAT32 ESP + p2 ext4, not a single FAT32 disk)...'
2026-08-09 22:14:01 +08:00
if ($Variant -in @('dev','both')) {
Write-Host '[build] === Building DEV image (QEMU test, dev_mode=1, debug on) ==='
Build-ImageVariant 'dev' $DevImagePath
Write-Host "[build] DEV image done: $DevImagePath"
}
if ($Variant -in @('release','both')) {
Write-Host '[build] === Building RELEASE image (real hardware, dev_mode=0, debug off) ==='
Build-ImageVariant 'release' $ReleaseImagePath
Write-Host "[build] RELEASE image done: $ReleaseImagePath"
}
if ($Variant -in @('realtest','both')) {
Write-Host '[build] === Building REALTEST image (real hardware, disk_log=1, debug on) ==='
Build-ImageVariant 'realtest' $RealtestImagePath
Write-Host "[build] REALTEST image done: $RealtestImagePath"
}
# Restore SYSTEM to dev config (dev is the primary development target)
if ($Variant -eq 'both') {
Write-Host '[build] Restoring SYSTEM to dev config for development...'
Build-ImageVariant 'dev' $null
}
# Sidecar AHCI test disk only. Not one of the ISO/*.img system images.
Write-Host '[build] Rebuilding SATA FAT32 sidecar (.build_tmp/sata_fat32_dsk.img, not ISO/*.img)...'
$BuildTmp = Join-Path $Root '.build_tmp'
$SidecarSh = Join-Path $Root 'CODE\linux\make_sata_sidecar.sh'
$SidecarImg = Join-Path $BuildTmp 'sata_fat32_dsk.img'
if ((Test-Path $SidecarSh) -and (Get-Command wsl -ErrorAction SilentlyContinue)) {
$wslSh = ConvertTo-WslUnixPath $SidecarSh
$wslSys = ConvertTo-WslUnixPath $SystemDir
$wslImg = ConvertTo-WslUnixPath $SidecarImg
& wsl -u root -- bash $wslSh $wslSys $wslImg
if ($LASTEXITCODE -ne 0) {
Write-Host "[build] WARNING: sidecar rebuild failed ($LASTEXITCODE), keeping old image"
} else {
Write-Host "[build] SATA IMG: $SidecarImg"
}
} else {
Write-Host '[build] WARNING: make_sata_sidecar.sh/WSL unavailable, SATA image not rebuilt'
}
2026-07-31 13:53:49 +08:00
# NVMe 测试盘镜像 (64MB, LBA0 带 DESHABNVME0 签名, 用于验证 NVMe block 数据路径)
$NvmeImg = Join-Path $BuildTmp 'nvme_test.img'
if (-not (Test-Path $NvmeImg)) {
Write-Host '[build] Creating NVMe test disk image...'
$nfs = [System.IO.File]::Open($NvmeImg, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
try {
$nfs.SetLength(64MB)
$sigSector = [byte[]]::new(512)
$sigText = [System.Text.Encoding]::ASCII.GetBytes('DESHABNVME0')
for ($i = 0; $i -lt 512; $i++) { $sigSector[$i] = $sigText[$i % $sigText.Length] }
Write-At $nfs 0 $sigSector
}
finally {
$nfs.Close()
}
Write-Host "[build] NVMe IMG: $NvmeImg"
}
# USB xHCI test disk image (64MB, for USB Mass Storage driver testing)
$UsbImg = Join-Path $BuildTmp 'usb_test.img'
if (-not (Test-Path $UsbImg)) {
Write-Host '[build] Creating USB test disk image...'
$ufs = [System.IO.File]::Open($UsbImg, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
try {
$ufs.SetLength(64MB)
$sigSector = [byte[]]::new(512)
$sigText = [System.Text.Encoding]::ASCII.GetBytes('DESHABUSB00')
for ($i = 0; $i -lt 512; $i++) { $sigSector[$i] = $sigText[$i % $sigText.Length] }
Write-At $ufs 0 $sigSector
}
finally {
$ufs.Close()
}
Write-Host "[build] USB IMG: $UsbImg"
}