|
- # Shared helpers for the sequential Nova128 editor/compiler launchers (E07-S02).
- # Import-Module (Join-Path $PSScriptRoot "lib\Nova128Launcher.psm1") -Force
-
- $script:ViceBin = "C:\Program Files\GTK3VICE-3.10-win64\bin"
-
- function Get-Nova128ViceTool([string]$Name) {
- $path = Join-Path $script:ViceBin "$Name.exe"
- if (-not (Test-Path -LiteralPath $path)) { throw "$Name.exe not found at $path" }
- return $path
- }
-
- # One writable session per image directory. The lock holds the owning PID and
- # kind; a lock whose process is gone is stale and is replaced.
- function Enter-Nova128ImageSession {
- param(
- [Parameter(Mandatory)][string]$Image,
- [Parameter(Mandatory)][ValidateSet("editor", "compiler")][string]$Kind
- )
- $folder = Split-Path ([IO.Path]::GetFullPath($Image)) -Parent
- New-Item -ItemType Directory -Force -Path $folder | Out-Null
- $lockPath = Join-Path $folder ".newlang-session.lock"
-
- if (Test-Path -LiteralPath $lockPath) {
- # A live holder keeps the file open without sharing, so even reading it
- # can fail. That is the same answer as a readable live PID.
- $text = ""
- try {
- $text = Get-Content -Raw -LiteralPath $lockPath
- } catch {
- throw "another Nova128 session already holds $Image"
- }
- $owner = 0
- $ownerKind = "another"
- if ($text -match '(?m)^(\d+)\s+(\S+)') {
- $owner = [int]$Matches[1]
- $ownerKind = $Matches[2]
- }
- $alive = $owner -gt 0 -and $null -ne (Get-Process -Id $owner -ErrorAction SilentlyContinue)
- if ($alive) {
- throw "the $ownerKind session (pid $owner) still owns $Image; close it before starting another"
- }
- Remove-Item -LiteralPath $lockPath -Force
- }
-
- try {
- $stream = [IO.File]::Open($lockPath, [IO.FileMode]::CreateNew,
- [IO.FileAccess]::Write, [IO.FileShare]::None)
- } catch {
- throw "another Nova128 session already holds $Image"
- }
- $writer = [IO.StreamWriter]::new($stream)
- $writer.WriteLine("$PID $Kind")
- $writer.Flush()
- return [pscustomobject]@{ Path = $lockPath; Stream = $stream; Writer = $writer
- Image = $Image; Kind = $Kind }
- }
-
- function Exit-Nova128ImageSession {
- param([Parameter(Mandatory)]$Session)
- try {
- $Session.Writer.Dispose()
- $Session.Stream.Dispose()
- } finally {
- if (Test-Path -LiteralPath $Session.Path) {
- Remove-Item -LiteralPath $Session.Path -Force
- }
- if ($Session.Image) {
- Wait-Nova128ImageFlush -Image $Session.Image
- }
- }
- }
-
- # The emulator has exited, but DOS writes may still be in flight. Return only
- # when the image can be opened exclusively.
- function Wait-Nova128ImageFlush {
- param(
- [Parameter(Mandatory)][string]$Image,
- [int]$TimeoutSeconds = 30
- )
- if (-not (Test-Path -LiteralPath $Image)) { return }
- $deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
- while ($true) {
- try {
- $stream = [IO.File]::Open($Image, [IO.FileMode]::Open,
- [IO.FileAccess]::Read, [IO.FileShare]::None)
- $stream.Dispose()
- return
- } catch {
- if ([DateTime]::UtcNow -ge $deadline) {
- throw "the emulator exited but $Image is still locked (image not flushed)"
- }
- Start-Sleep -Milliseconds 200
- }
- }
- }
-
- # Start an emulator and wait for it to exit. Autostart uses the host program
- # path, so the program is deterministic; the disk image stays attached for the
- # session's own reads and writes.
- # Write one file into a disk image and prove it appears in the directory.
- function Add-Nova128DiskFile {
- param(
- [Parameter(Mandatory)][string]$Image,
- [Parameter(Mandatory)][string]$HostPath,
- [Parameter(Mandatory)][string]$DiskName,
- [ValidateSet("prg", "seq")][string]$Type = "prg"
- )
- $c1541 = Get-Nova128ViceTool "c1541"
- if (-not (Test-Path -LiteralPath $HostPath -PathType Leaf)) {
- throw "file not found: $HostPath"
- }
- $destination = if ($Type -eq "seq") { "$DiskName,s" } else { $DiskName }
- $output = (& $c1541 $Image -write $HostPath $destination 2>&1) -join "`n"
- if ($LASTEXITCODE -ne 0) {
- throw "c1541 could not write '$DiskName' to $Image`n$output"
- }
- $listing = (& $c1541 $Image -list 2>&1) -join "`n"
- $pattern = '(?im)"' + [regex]::Escape($DiskName) + '"\s+' + $Type
- if ($listing -notmatch $pattern) {
- throw "$DiskName ($Type) is missing from $Image after writing"
- }
- }
-
- function Invoke-Nova128Emulator {
- param(
- [Parameter(Mandatory)][ValidateSet("x64sc", "x128")][string]$Emulator,
- [Parameter(Mandatory)][string]$Program,
- [string]$DiskImage,
- [string]$Screenshot,
- [int]$LimitCycles = 0,
- [switch]$Warp,
- [switch]$EightyColumn,
- [switch]$ProgramOnDisk
- )
- $tool = Get-Nova128ViceTool $Emulator
- $workingDirectory = $null
- $autostart = $Program
- if ($ProgramOnDisk) {
- # The editor takes its write device from the disk it was loaded from.
- # Autostarting the copy on the session disk is what makes its saves
- # reach the image; a host-injected program has no such device.
- if (-not $DiskImage) { throw "a disk-resident program needs -DiskImage" }
- $autostart = [IO.Path]::GetFileName($DiskImage) + ":" + $Program
- $workingDirectory = Split-Path ([IO.Path]::GetFullPath($DiskImage)) -Parent
- } elseif (-not (Test-Path -LiteralPath $Program)) {
- throw "program not found: $Program"
- }
-
- $arguments = @()
- if ($DiskImage) {
- if (-not (Test-Path -LiteralPath $DiskImage)) {
- throw "disk image not found: $DiskImage"
- }
- $arguments += @("-drive8type", "1581", "-drive8truedrive", "-8",
- ('"' + $DiskImage + '"'))
- }
- if ($EightyColumn) { $arguments += "-80col" }
- $arguments += @("-autostart", ('"' + $autostart + '"'), "-autostart-warp",
- "-sounddev", "dummy")
- if ($Warp) { $arguments += "-warp" }
- if ($LimitCycles -gt 0) { $arguments += @("-limitcycles", "$LimitCycles") }
- if ($Screenshot) {
- if (Test-Path -LiteralPath $Screenshot) {
- Remove-Item -LiteralPath $Screenshot -Force
- }
- # x64sc's plain -exitscreenshot is the VIC-II screen. On x128 the
- # 80-column editor draws on the VDC, which only the plain option
- # captures; -exitscreenshotvicii would save a blank 40-column screen.
- $shotOption = if ($Emulator -eq "x64sc" -or $EightyColumn) {
- "-exitscreenshot"
- } else {
- "-exitscreenshotvicii"
- }
- $arguments += @($shotOption, ('"' + $Screenshot + '"'))
- }
-
- Write-Host ("launching {0} with {1}" -f $Emulator, $autostart)
- $startArgs = @{ FilePath = $tool; ArgumentList = $arguments; PassThru = $true; Wait = $true }
- if ($workingDirectory) { $startArgs.WorkingDirectory = $workingDirectory }
- $process = Start-Process @startArgs
- if ($Screenshot -and -not (Test-Path -LiteralPath $Screenshot)) {
- throw "$Emulator did not create the exit screenshot $Screenshot"
- }
- if ($DiskImage) { Wait-Nova128ImageFlush -Image $DiskImage }
- return $process.ExitCode
- }
-
- Export-ModuleMember -Function Get-Nova128ViceTool, Enter-Nova128ImageSession,
- Exit-Nova128ImageSession, Wait-Nova128ImageFlush, Add-Nova128DiskFile,
- Invoke-Nova128Emulator
|