25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
9.9KB

  1. # Shared helpers for Nova128 build and x128 verification scripts.
  2. # Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
  3. $script:ViceBin = "C:\Program Files\GTK3VICE-3.10-win64\bin"
  4. function Get-ViceTool([string]$Name) {
  5. $path = Join-Path $script:ViceBin "$Name.exe"
  6. if (-not (Test-Path -LiteralPath $path)) { throw "$Name.exe not found at $path" }
  7. return $path
  8. }
  9. # Assemble each source and link them with an ld65 config.
  10. # Returns @{ Program; Map; Labels }.
  11. function Invoke-Nova128Link {
  12. param(
  13. [Parameter(Mandatory)][string]$Name,
  14. [Parameter(Mandatory)][string[]]$Sources,
  15. [Parameter(Mandatory)][string]$Config,
  16. [Parameter(Mandatory)][string]$BuildDir,
  17. [string[]]$Defines = @()
  18. )
  19. $ca65 = (Get-Command ca65 -ErrorAction Stop).Source
  20. $ld65 = (Get-Command ld65 -ErrorAction Stop).Source
  21. New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null
  22. $out = @{
  23. Program = Join-Path $BuildDir "$Name.prg"
  24. Map = Join-Path $BuildDir "$Name.map"
  25. Labels = Join-Path $BuildDir "$Name.lbl"
  26. }
  27. foreach ($path in $out.Values) {
  28. if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force }
  29. }
  30. $defineArgs = @()
  31. foreach ($d in $Defines) { $defineArgs += @("-D", $d) }
  32. $objects = @()
  33. foreach ($source in $Sources) {
  34. $object = Join-Path $BuildDir ("$Name-" + [IO.Path]::GetFileNameWithoutExtension($source) + ".o")
  35. & $ca65 --cpu 6502 @defineArgs -o $object $source
  36. if ($LASTEXITCODE -ne 0) { throw "ca65 failed on $source" }
  37. $objects += $object
  38. }
  39. $linkOutput = & $ld65 -C $Config -o $out.Program -m $out.Map -Ln $out.Labels @objects 2>&1
  40. if ($LASTEXITCODE -ne 0) { throw ("ld65 failed:`n" + ($linkOutput | Out-String)) }
  41. return $out
  42. }
  43. function Get-Nova128Label {
  44. param([Parameter(Mandatory)][string]$Labels, [Parameter(Mandatory)][string]$Name)
  45. $text = Get-Content -Raw -LiteralPath $Labels
  46. $m = [regex]::Match($text, "(?im)^al\s+([0-9a-f]+)\s+\." + [regex]::Escape($Name) + "\s*$")
  47. if (-not $m.Success) { throw "label $Name not found in $Labels" }
  48. return [Convert]::ToInt32($m.Groups[1].Value, 16)
  49. }
  50. # ASCII text -> PETSCII SEQ bytes, using the same rules as Vim and
  51. # prepare-disk.ps1: a-z -> $41-$5A, A-Z -> $C1-$DA, CRLF/LF -> $0D.
  52. function ConvertTo-Nova128Petscii {
  53. param([Parameter(Mandatory)][AllowEmptyString()][string]$Text)
  54. $bytes = [Text.Encoding]::ASCII.GetBytes($Text)
  55. $out = New-Object System.Collections.Generic.List[byte]
  56. for ($i = 0; $i -lt $bytes.Length; $i++) {
  57. $v = $bytes[$i]
  58. if ($v -eq 13) {
  59. if ($i + 1 -lt $bytes.Length -and $bytes[$i + 1] -eq 10) { continue }
  60. $out.Add(13); continue
  61. }
  62. if ($v -eq 10) { $out.Add(13); continue }
  63. if ($v -ge 0x61 -and $v -le 0x7A) { $out.Add([byte]($v - 0x20)); continue }
  64. if ($v -ge 0x41 -and $v -le 0x5A) { $out.Add([byte]($v + 0x80)); continue }
  65. $out.Add($v)
  66. }
  67. return ,$out.ToArray()
  68. }
  69. # Create a fresh disk image and write files to it.
  70. # $Files: ordered list of @{ Name = "decls.n8"; Path = host file; Type = "seq"|"prg" }
  71. function New-Nova128Disk {
  72. param(
  73. [Parameter(Mandatory)][string]$Path,
  74. [Parameter(Mandatory)][object[]]$Files,
  75. [ValidateSet("d64", "d81")][string]$Type = "d81",
  76. [string]$Label = "nova128,n8"
  77. )
  78. $c1541 = Get-ViceTool "c1541"
  79. if (Test-Path -LiteralPath $Path) { Remove-Item -LiteralPath $Path -Force }
  80. $c1541Args = @("-format", $Label, $Type, $Path)
  81. foreach ($f in $Files) {
  82. $suffix = if ($f.Type -eq "seq") { ",s" } else { "" }
  83. $c1541Args += @("-write", $f.Path, ($f.Name + $suffix))
  84. }
  85. $output = & $c1541 @c1541Args 2>&1
  86. if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $Path)) {
  87. throw ("c1541 failed:`n" + ($output | Out-String))
  88. }
  89. $listing = (& $c1541 -attach $Path -list 2>&1) | Out-String
  90. foreach ($f in $Files) {
  91. if ($listing -notmatch ('"' + [regex]::Escape($f.Name) + '"\s+' + $f.Type)) {
  92. throw "$($f.Name) ($($f.Type)) missing from $Path"
  93. }
  94. }
  95. }
  96. # Start x128 with a remote monitor. Returns a session object.
  97. function Start-Nova128X128 {
  98. param(
  99. [Parameter(Mandatory)][string]$Autostart,
  100. [int]$Port = 6530,
  101. [string]$Drive8Image,
  102. [string]$Drive8Type = "1581",
  103. [string]$Screenshot,
  104. [string]$Keys = "",
  105. [int]$KeyDelay = 4000
  106. )
  107. $x128 = Get-ViceTool "x128"
  108. $argList = @()
  109. if ($Drive8Image) {
  110. $argList += @("-drive8type", $Drive8Type, "-drive8truedrive", "-8", ('"' + $Drive8Image + '"'))
  111. }
  112. $argList += @("-autostart", ('"' + $Autostart + '"'), "-autostart-warp", "-warp",
  113. "-sounddev", "dummy", "-remotemonitor",
  114. "-remotemonitoraddress", "ip4://127.0.0.1:$Port")
  115. if ($Keys) {
  116. $argList += @("-keybuf", ('"' + $Keys + '"'), "-keybuf-delay", "$KeyDelay")
  117. }
  118. if ($Screenshot) {
  119. if (Test-Path -LiteralPath $Screenshot) { Remove-Item -LiteralPath $Screenshot -Force }
  120. $argList += @("-exitscreenshotvicii", ('"' + $Screenshot + '"'))
  121. }
  122. $psi = [Diagnostics.ProcessStartInfo]::new()
  123. $psi.FileName = $x128
  124. $psi.Arguments = $argList -join ' '
  125. $psi.UseShellExecute = $true
  126. $psi.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
  127. $process = [Diagnostics.Process]::Start($psi)
  128. $client = $null
  129. $deadline = [DateTime]::UtcNow.AddSeconds(10)
  130. while ([DateTime]::UtcNow -lt $deadline) {
  131. if ($process.HasExited) { throw "x128 exited before the remote monitor connected" }
  132. try {
  133. $client = [Net.Sockets.TcpClient]::new()
  134. $client.Connect("127.0.0.1", $Port)
  135. break
  136. } catch {
  137. if ($client) { $client.Dispose(); $client = $null }
  138. Start-Sleep -Milliseconds 200
  139. }
  140. }
  141. if (-not $client) { $process.Kill(); throw "could not connect to the VICE remote monitor" }
  142. $stream = $client.GetStream()
  143. $writer = [IO.StreamWriter]::new($stream, [Text.Encoding]::ASCII, 1024, $true)
  144. $writer.NewLine = "`n"
  145. $writer.AutoFlush = $true
  146. return [pscustomobject]@{ Process = $process; Client = $client; Stream = $stream; Writer = $writer }
  147. }
  148. function Invoke-Nova128Monitor {
  149. param([Parameter(Mandatory)]$Session, [Parameter(Mandatory)][string]$Command,
  150. [int]$TimeoutMs = 5000)
  151. $Session.Writer.WriteLine($Command)
  152. $buffer = New-Object byte[] 65536
  153. $text = ""
  154. # Every monitor reply ends with a prompt such as "(C:$1c0d) ".
  155. $deadline = [DateTime]::UtcNow.AddMilliseconds($TimeoutMs)
  156. while ([DateTime]::UtcNow -lt $deadline) {
  157. if ($Session.Stream.DataAvailable) {
  158. $n = $Session.Stream.Read($buffer, 0, $buffer.Length)
  159. if ($n -le 0) { break }
  160. $text += [Text.Encoding]::ASCII.GetString($buffer, 0, $n)
  161. if ($text -match '\(C:\$[0-9a-f]{4}\)\s*$') { break }
  162. } else {
  163. Start-Sleep -Milliseconds 50
  164. }
  165. }
  166. return $text
  167. }
  168. # Read memory through the monitor. Returns byte[] (length = Count).
  169. # Reads RAM bank 0 by default, not the CPU's current view: after a program
  170. # returns to BASIC, $4000-$BFFF shows BASIC ROM in the CPU view.
  171. function Read-Nova128Memory {
  172. param([Parameter(Mandatory)]$Session, [int]$Address, [int]$Count, [string]$Bank = "ram")
  173. Invoke-Nova128Monitor $Session "bank $Bank" | Out-Null
  174. $timeout = 5000 + 2 * $Count # large dumps print slowly
  175. $text = Invoke-Nova128Monitor $Session ("m `${0:X4} `${1:X4}" -f $Address, ($Address + $Count - 1)) -TimeoutMs $timeout
  176. $memory = @{}
  177. foreach ($row in [regex]::Matches($text, "(?i)C:([0-9a-f]{4})((?:[ ]{1,2}[0-9a-f]{2}){1,16})")) {
  178. $a = [Convert]::ToInt32($row.Groups[1].Value, 16)
  179. foreach ($hex in ($row.Groups[2].Value.Trim() -split '\s+')) {
  180. $memory[$a] = [Convert]::ToByte($hex, 16); $a++
  181. }
  182. }
  183. $bytes = New-Object byte[] $Count
  184. for ($i = 0; $i -lt $Count; $i++) {
  185. if (-not $memory.ContainsKey($Address + $i)) {
  186. throw ("monitor did not return `${0:X4}; response: {1}" -f ($Address + $i), $text)
  187. }
  188. $bytes[$i] = $memory[$Address + $i]
  189. }
  190. return ,$bytes
  191. }
  192. # Poll the test mailbox ($1BF0) until it reports PASS or FAIL. Returns its
  193. # 16 bytes. Resumes emulation between polls.
  194. function Wait-Nova128Mailbox {
  195. param([Parameter(Mandatory)]$Session, [int]$TimeoutSeconds = 60)
  196. $deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
  197. while ($true) {
  198. Start-Sleep -Seconds 1
  199. $mb = Read-Nova128Memory $Session 0x1BF0 16
  200. if ($mb[0] -eq 0x4E -and $mb[1] -eq 0x38 -and ($mb[3] -eq 0xA5 -or $mb[3] -eq 0xFA)) {
  201. return ,$mb
  202. }
  203. if ([DateTime]::UtcNow -ge $deadline) {
  204. $state = if ($mb[0] -eq 0x4E) { "status `$" + $mb[3].ToString("X2") } else { "never initialized" }
  205. throw "mailbox did not finish within $TimeoutSeconds s ($state)"
  206. }
  207. Invoke-Nova128Monitor $Session "x" | Out-Null
  208. }
  209. }
  210. # Let the program finish printing, then quit x128 (writes any exit screenshot).
  211. function Stop-Nova128X128 {
  212. param([Parameter(Mandatory)]$Session, [int]$SettleSeconds = 2)
  213. try {
  214. if ($SettleSeconds -gt 0) {
  215. Invoke-Nova128Monitor $Session "x" | Out-Null
  216. Start-Sleep -Seconds $SettleSeconds
  217. Invoke-Nova128Monitor $Session "m 0000 0000" | Out-Null
  218. }
  219. $Session.Writer.WriteLine("quit")
  220. $Session.Writer.Dispose()
  221. $Session.Client.Dispose()
  222. if (-not $Session.Process.WaitForExit(10000)) { throw "x128 did not exit" }
  223. } finally {
  224. if (-not $Session.Process.HasExited) { $Session.Process.Kill(); $Session.Process.WaitForExit() }
  225. }
  226. }
  227. Export-ModuleMember -Function Get-ViceTool, Invoke-Nova128Link, Get-Nova128Label,
  228. ConvertTo-Nova128Petscii, New-Nova128Disk, Start-Nova128X128, Invoke-Nova128Monitor,
  229. Read-Nova128Memory, Wait-Nova128Mailbox, Stop-Nova128X128

Powered by TurnKey Linux.