|
- # 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.
- function Invoke-Nova128Emulator {
- param(
- [Parameter(Mandatory)][ValidateSet("x64sc", "x128")][string]$Emulator,
- [Parameter(Mandatory)][string]$Program,
- [string]$DiskImage,
- [string]$Screenshot,
- [int]$LimitCycles = 0,
- [switch]$Warp
- )
- $tool = Get-Nova128ViceTool $Emulator
- if (-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 + '"'))
- }
- $arguments += @("-autostart", ('"' + $Program + '"'), "-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 names the VIC-II exit screenshot differently from x128.
- $shotOption = if ($Emulator -eq "x64sc") { "-exitscreenshot" } else { "-exitscreenshotvicii" }
- $arguments += @($shotOption, ('"' + $Screenshot + '"'))
- }
-
- Write-Host ("launching {0} with {1}" -f $Emulator, $Program)
- $process = Start-Process -FilePath $tool -ArgumentList $arguments -PassThru -Wait
- 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, Invoke-Nova128Emulator
|