Effectus na starych linuksach.

Zdarza mi się ostatnio testować i kompilować (na prośbę przesympatycznego autora) Effectusa, czyli cross compiler Atari Action! na PC dla Linuksa.

Zastanawiałem się jak wypada kompatybilność statycznych binarek, które kompiluje na Archu. No i wg moich ustaleń na wszystkim co ma glibc w wersji 2 powinno działać.
Testy przeprowadziłem na dystrybucji którą znam i której kiedyś używałem: Slackware w wersjach 1.0.1, 2.2.1 i 7.0.

Wybór był nieprzypadkowy. Na początku myślałem, że wystarczy w kernelu obsługa formatu binarnego ELF. Slackware 1.0.1 z kernelem 0.99.12 jeszcze tego formatu nie obsługiwał. Kolejny, czyli Slackware 2.2.1 miał już kernel 1.2.1 czyli ELF na 100% obsługiwany. Segfaulty sugerowały, że to jednak libc się gryzie. Kiedyś dawno zrobiłem taki eksperyment na potrzeby dyskusji o ABI kernela i moje programiki działały z kernelem 0.1, ale one nie używały libc, tylko wprost funkcje os-u.

Pierwszym Slackiem z glibc2 była wersja 7, od ktorej zaczęła się moja poważniejsza przygoda z Linuksem. Rok 1999, full-wypas kernel 2.2. No i jak się spodziewałem, binarki effectusa i madsa zadziałały bez problemu.

A tak swoją drogą instalacja Linuksa z 1993 w 2014 to ciekawe doświadczenie. Qemu nie jest w stanie zaemulować kontrolera scsi, czy też obsługiwanego 20 lat temu CD romu, ale to nie problem. Można stworzyć obraz dysku w pliku (dd) i w nim utworzyć system plików, np msdos. W ten sposób stworzyłem dysk zawierający wszystkie serie pakietow (można taki obraz zamontować z -o loop=/dev/loop0), który potem montowałem w trakcie instalacji do /root/dos, a instalator pozwala na instalację pakietów ze wskazanego, wcześniej zamontowanego nośnika. Wygodne to i szybkie rozwiązanie, a dziala chyba we wszystkich wersjach Slackware.

Niesamowite jest to, jak przyjemnym w użytkowaniu był Slackware już w 93 roku ubiegłego stulecia…

A działanie binarek effectusa/madsa  na dystrybucji z 1999 roku, czyli sprzed 15 lat daje do myślenia.

VBXE + Action! – Mapa atrybutów.

Kolejny „przełomowy” przykład na podstawie kodu Rybags’a w Basicu. Film ponizej pokazuje szybkosc dzialania w Atari Basic vs Action!

set 14=$8000
set $491=$8000
include "H1:RUNTIME.ACT"

proc main()
 byte vc=$D640, memcont=$D65E, membank=$D65F
 byte xdla0=$D641, xdla1=$D642, xdla2=$D643
 byte pointer p=$A000
 byte i, j, curinh=752
 int bc, lum, ls
 card cs

 if vc<>$10 then
  printe("Brak VBXE FX")
  do od
 fi

 memcont=$A8
 membank=128

 p^=40 p==+1
 p^=14 p==+1
 p^=7 p==+1
 p^=0 p==+1
 p^=16 p==+1
 p^=0 p==+1
 p^=0 p==+1
 p^=0 p==+1
 p^=0 p==+1
 p^=0 p==+1
 p^=7 p==+1
 p^=7 p==+1
 p^=2 p==+1
 p^=255 p==+1

 for i=0 to 75 step 3
  do
   p^=40 p==+1
   p^=0 p==+1
   p^=7 p==+1
  od

 p^=0 p==+1
 p^=128

 membank=129

 for i=0 to 23
  do
   cs=$A000+i*80 lum=6 ls=2 bc=0
   for j=0 to 39 do
    p=cs+j*4
    p^=0 p==+1
    p^=lum p==+1
    lum==+ls
    if lum=15 or lum=6 then
     ls=-ls
    fi
    p^=bc p==+1
    bc==+12
    if bc>255 then bc==-255 fi
    p^=0
  od
 od

 xdla0=0 xdla1=0 xdla2=0

 graphics(0)
 curinh=1
 vc=3

 printf("%EVBXE is cool!")
 do od
return

Różne wartości MAPSTEP i przyrostu bc dają różne efekty:

Od razu przychodzi do głowy żeby te wartości zmieniać w VBI…

VBXE + Action! Animowane sprite’y.

Choć w zasadzie powinienem napisać jeden sprite 🙂

set 14=$8000
set $491=$8000
include "H1:RUNTIME.ACT"

MODULE
int fno=[1], i=[0]
byte bltst=$D653
byte bltmv0=$A115, bltmv1=$A116, bltmv2=$A117

byte array bltsrc =
   [ 0 96 1
   128 96 1
     0 97 1
   128 97 1 ]

proc CIOVE=$E4DF(byte areg, xreg)

proc delay()
 card i
 for i=0 to 30000 do od
 return

proc bltme()
 if fno mod 10 = 0 then
  bltmv0=bltsrc(i*3)
  bltmv1=bltsrc(i*3+1)
  bltmv2=bltsrc(i*3+2)
  bltst=1 fno==+1 i==+1
  if i > 3 then
   fno=1 i=0
  fi
 [$4C $E462]
 fi
 fno==+1
[$4C $E462]

proc main()
 byte vc=$D640, clock=$14
 int i byte memcont=$D65E, membank=$D65F
 byte psel=$D645, csel=$D644, cr=$D646, cg=$D647, cb=$D648
 byte xdla0=$D641, xdla1=$D642, xdla2=$D643
 byte blt0=$D650, blt1=$D651, blt2=$D652 ; bltst=$D653
 byte bkg=710, curinh=752
 byte iocb1cmd=850
 card iocb1buf=852, iocb1len=856
 byte nmien=$D40E
 card vvblkd=$0224

 byte array xdl=[98 136 219 0 0 0 64 1 17 223]

 byte array clrscr=
   [ 0 0 0     ;src addr
       0 0     ;src step y
         0     ;src step x
     0 0 0     ;dst addr ($0000 up)
      64 1     ;dst step y (320)
         1     ;dst step x
      63 1     ;width (320-1)
       239     ;height (240-1)
         0     ;and mask
         0     ;xor mask
         0     ;collision and mask
         0     ;zoom
         0     ;pattern
         0 ]   ;control

 byte array bltmv=
  [ 0 96 1     ;src addr
       0 2     ;src step y
         1     ;src step x
  224 62 0     ;dst addr
      64 1     ;dst step y
         1     ;dst step x
     124 0     ;width
        53     ;height
       255     ;and mask
         0     ;xor mask
         0     ;collision and mask
         0     ;zoom
         0     ;pattern
         0 ]   ;control

 if vc<>$10 then
  printe("Brak VBXE FX")
  delay()
  [$4C $C2AA] ;reset
 fi

 memcont=$A8
 membank=128+20
 moveblock($A000, xdl, 10)
 xdla0=0 xdla1=64 xdla2=1

 moveblock($A100, clrscr, 21)
 blt0=0 blt1=65 blt2=1
 bltst=1 ;blitter's clear scr
 while bltst <> 0 do od
 moveblock($A115, bltmv, 21)
 blt0=21 blt1=65 blt2=1

 graphics(0)
 bkg=0 curinh=1 put(31)

 close(1)
 open(1,"D1:HEL.PAL",4,0)
 psel=1 csel=0

 for i=0 to 255 do
  cr=getd(1) cg=getd(1) cb=getd(1)
 od
 close(1)

 open(1,"D1:HEL.PIC",4,0)
 iocb1cmd=7
 iocb1buf=$A000
 iocb1len=$1000

 for i=22 to 29 do
  membank=128+i
  CIOVE(0,$10)
 od

 close(1)
 vc=3
 membank=128+20

 i=clock while clock=i do od
 nmien=0
 vvblkd=bltme
 nmien=$40
 do od
return

VBXE + Action! Wczytywanie obrazu w overlay.

No i jest, pierwszy eksperyment w Action! + VBXE: obrazek w tle:

set 14=$8000
set $491=$8000

include "H1:RUNTIME.ACT"

proc CIOVE=$E4DF(byte areg, xreg)

proc delay()
 card i
 for i=0 to 30000 do od
return

proc main()

 byte vc=$D640
 int i byte memcont=$D65E, membank=$D65F
 byte psel=$D645, csel=$D644, cr=$D646, cg=$D647, cb=$D648
 byte xdla0=$D641, xdla1=$D642, xdla2=$D643
 byte blt0=$D650, blt1=$D651, blt2=$D652, bltst=$D653
 byte bkg=710, curinh=752
 byte iocb1cmd=850
 card iocb1buf=852, iocb1len=856

 byte array xdl=[98 136 219 0 2 0 64 1 17 223]

 ;clrscr blitterlist
 byte array clrscr=
   [ 0 0 0  ;src addr
     0 0    ;src step y
     0      ;src step x
     0 0 0  ;dst addr ($0000 up)
     64 1   ;dst step y (320)
     1      ;dst step x
     63 1   ;width (320-1)
     239    ;height (240-1)
     0      ;and mask
     0      ;xor mask
     0      ;collision and mask
     0      ;zoom
     0      ;pattern
     0 ]    ;control

 if vc<>$10 then
  printe("No VBXE FX found")
  delay()
  [$4C $C2AA] ;warm reset
 fi

 memcont=$A8  ;memac at $A000, cpu access
 membank=128+20
 moveblock($A000, xdl, 10)
 xdla0=0 xdla1=64 xdla2=1

 moveblock($A100, clrscr, 21)
 blt0=0 blt1=65 blt2=1
 bltst=1 ;blitter start will clear scr
 while bltst <> 0 do od ;let's wait till
                        ;blitter finishes
 graphics(0)
 bkg=0 curinh=1 put(31)

 close(1)
 open(1,"D1:AN.PAL",4,0)
 psel=1 csel=0
 for i=0 to 255 do
  cr=getd(1) cg=getd(1) cb=getd(1)
 od
 close(1)

 open(1,"D1:AN.PIC",4,0)
 iocb1cmd=7
 iocb1buf=$A000
 iocb1len=$1000

 for i=2 to 18 do
  membank=128+i
  CIOVE(0,$10)
 od
 close(1)

 vc=3
 printe("Hej czesc! Daj cos zjesc!")
 do od
return

test

Więcej informacji w tym wątku:

http://atarionline.pl/forum/comments.php?DiscussionID=2623&page=1#Item_15

Z moich przemyśleń dodam tylko uwagę na temat adresu overlay w xdl. Adres overlay w tym przykładzie to 0 2 0, czyli 2^9=512. Obrazek kopiujemy od adresu $2000=8192 i jeśli ustawiłbym  overlay na 0 32 0 to obraz wyświetlany byłby od samej góry, brzydko trochę, nie? Przy pierwszym podejściu ustawiałem ten adres na 0 0 0 i zastanawiałem się, czemu obrazek jest przesunięty? Po dwóch tygodniach naszła mnie właściwa myśl… Otóż, adres overlay musi być taki, aby 8192-adres było podzielne przez 320 (tada!). Czyli np. jeśli chcemy przed obrazkiem 24 linie, to 8192-24*320=512. W powyższym przykładzie blitter zeruje pamięć od $0000 więc mamy ładną ramkę przed i po obrazku.

PMG w Action!

Najwyższy czas na coś nowego. Poniżej pierwszy przykład PMG w Action! na podstawie „Atari Graphics and Arcade Game Design”. Do zerowania pamięci użyłem funkcji zero(), a do kopiowania danych moveblock() z Runtime Action!  Przy przesuwie pionowym duszka dane kopiujemy za pomocą wskaźników (tak btw, to lubię wskaźniki w Action!).


byte array p1data = [0 0 153 153 189 189 255 255 189 189 153 153 0 0]
card spot
byte pointer p1, p2

proc delay()
 int i for i=0 to 1000 do od
return

proc moveup()
 int i
 p1=spot p2=p1-1
 for i=0 to 13 do
  p2^=p1^
  p1==+1 p2==+1
 od
 spot==-1
return

proc movedown()
 int i
 p1=spot+13 p2=p1+1
 for i=0 to 13 do
  p2^=p1^
  p1==-1 p2==-1
 od
 spot==+1
return

proc main()
 byte pm=106, dmactl=559, gractl=53277, apmbase=54279,
      p1size=53256, p1hpos=53248, p1col=704, p1chpos,
      stick0
 card pmbase

 graphics(1+16)
 pm==-8
 pmbase=pm*256
 dmactl=62
 gractl=3
 apmbase=pm
 p1hpos=50 p1chpos=50
 p1size=1
 p1col=88
 spot=pmbase+1024+50

 zero(pmbase+1024, 256)
 moveblock(spot, p1data, 14)

 do
  stick0=stick(0)
  if stick0=11 then
   p1chpos==-1 p1hpos=p1chpos
  elseif stick0=7 then
   p1chpos==+1 p1hpos=p1chpos
  elseif stick0=14 then
   moveup()
  elseif stick0=13 then
   movedown()
 fi
 delay()
od

return

Jako ciekawostkę polecam zakomentować delay(). Action jest chyba jednak dość szybkie…

VBI finescroll + DLI rainbow (ASM)

Od piątku leżę w szpitalnym łóżku (spokojnie, to tylko kolejne badania moich szwankujących mięśni) i pomimo tego, że sobotę i niedzielę spędziłem z dzieciakami w domu (przypomina mi się seria „Wielkie ucieczki” na TVN), trochę mi się nudzi. Dzisiaj mnie biopsnęli w nogę, więc może trochę pod wpływem anestetyków postanowiłem dokończyć mój ulubiony (ad nauseam) scroll poziomy w VBI i „rainbow effect” w DLI, tym razem w assemblerze i tym razem w lewo.

hscrol = 54276
sdmctl = $022F
dlist = 560
nmien = $D40E
vvblkd = $0224
xitvbv = $E462
scount = $8000
colt = $D017
indx = $8002
wsync = $D40A
vdslist = $0200

      org $4000

init  ldy #0
      sty sdmctl                                ; wyłącz antic
      lda <ndl                                  ; ustaw adres tablicy ndl jako dliste
      sta dlist
      lda >ndl
      sta dlist+1
      lda #16
      sta scount                                ; wyzeruj liczniki
      sty chrno
      sty indx
      lda #64
      sta 88
      lda #156
      sta 89

      ; vbi+dli
      lda <scroll                               ; ustaw scroll w opóźnionym vblank interrupt
      sta vvblkd
      lda >scroll
      sta vvblkd+1
      lda <dli                                  ; ustaw adres procedury dli
      sta vdslist
      lda >dli
      sta vdslist+1
      lda #42
      sta sdmctl                                ; włącz antic
      lda #$c0                                  ; włącz przerwania dli
      sta nmien
      ; kolory i napisy
      lda #0
      sta 710
      posxy #2, #5
      putline #txt1
      lda #50
      sta 88
      lda #155
      sta 89
      posxy #0, #0
      putline #txt2

loop  jmp loop

scroll ldy scount
       dey
       sty hscrol
       beq @+
       sty scount
       jmp xitvbv
@      ldy chrno
       iny
       cpy #30
       bne chmem
       lda #25
       ldx #0
       stx chrno
       sta ndl[28],x
       lda #155
       sta ndl[29],x
       ldx #16
       stx scount
       jmp xitvbv
chmem
       clc
       sty chrno
       ldx #0
       lda ndl[28],x
       adc #2
       sta ndl[28],x
       lda ndl[29],x
       adc #0
       sta ndl[29],x
       ldy #15
       sty hscrol
       iny
       sty scount
       jmp xitvbv

dli    pha
       txa
       pha
       tya
       pha
       inc indx
       ldx #7
@      lda #1
       sta wsync
       lda indx
       cmp #30
       beq @+
       dex
       cpx #0
       beq ret
       clc
       adc vcount
       sta colt
       jmp @-
@      ldy #0
       sty indx
       jmp @-1
ret    pla
       tay
       pla
       tax
       pla
       rti

scount .by 0
chrno .by 0

.array ndl [33] .byte
 112, 112, 112, 66, 64, 156, 2, 2, 2, 2, 6, 2, 2
 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 130, 86, 30, 155
 65, 32, 156
.enda

txt1  .by 'test finescroll!' $9b
txt2  .by 'mads compiler!' $9b

.link 'putline.obx'
.link 'posxy.obx'

a2

Powyższy przykład przesuwa druga z linii w trybie Antic 6/GR. 1, ale sporo zabawy zajęło mi uzyskanie prawidłowego finescroll w Antic 2 / GR. 0. Z tego co udało mi się wyeksperymentować, w trybie GR. 0 jeden piksel potrzebuje pół cyklu koloru, skoro tych pikseli jest 8, do przesunięcia jednego znaku potrzeba 4 zmiany rejestru finescroll (1 cykl koloru = 1 finescroll). Analogicznie w trybie GR. 1 / Antic 6, jeden piksel potrzebuje jeden cykl koloru, znak ma 8 pikseli szerokości, więc mamy finescroll o 8 per znak.

Warto wspomnieć o jeszcze jednym zjawisku. W przykładach w Action! i assemblerze scroll działał prawidłowo, ale tylko w prawo i tylko o 16 punktów. Wydawało mi się to dziwne i długo nie dawało spokoju dlaczego tak się dzieje, że przy finescroll o 8 widać coś jakby szarpnięcie o jeden znak. Zjawisko brało się stąd, że zmieniałem adres pamięci w display liście i wychodziłem z procedury, a zmiana rejestru hscroll na odpowiadający nowemu położeniu znaków na ekranie, następowała dopiero przy następnym vblank interrupt (czyli hscroll był nadal 0, a ANTIC przez chwilę do następnego vbi wyświetlał znak przesunięty po zmianie adresu). Zmiana adresu pamięci i jednoczesne ustawienie hscroll przed jmp xitvbv usunęło problem i wszystko działa tak jak w książce…

Reasumując, Mads jest moim ulubionym cross-assemblerem i pewne jego cechy i funkcje bardzo mnie zachęcają do dalszych eksperymentów, jednak coraz większy widzę sens w programowaniu w Action! Przede wszystkim czytelność kodu jest o wiele większa i na pierdoły nie traci się wiele czasu (tutaj ten sam efekt w Action!). Ciekaw jestem jak sprawdzi się Action! + VBXE.

PS: putline i posxy pochodzą z LIBRARIES/stdio/lib mads. Możnaby jeszcze wyzerować pamięć obrazu, ale… X /C w SDX zrobi to za mnie ;).

Action! Jedno RUNTIME, wiele modułów.

To już chyba ostatni wpis  z „linkowaniem” za pomocą type lub cat, który jednak pokaże do czego może się przydać Symbol Table Lister.  Załóżmy, że sklejamy kilka kawałków/modułów w Action! Fajnie byłoby aby Runtime było dołączane tylko raz – wymyśliłem sobie, że skoro STL pokaże mi adresy procedur w Runtime, to można zrobić tak:

  • Uruchomić w monitorze R: „H1:STL.ACT”
  • Otworzyć w edytorze Ctrl+Shift+R H1:RUNTIME.ACT i dodać np. set 14=$8000 set $491=$8000
  • Przejść do monitora Ctrl+Shift+M, skompilować C, zapisać W „H1:RUNTIME.OBX”

W LISTING.TXT (patrz poprzedni wpis o STL) znajdziemy adresy:

PrintE……… $822C PROC(BYTE ARRAY)
PrintF……… $8430 PROC(BYTE ARRAY, CARD, CARD, CARD, CARD, CARD)

Więc w naszym programie możemy zdefiniować:

set 14=$8739
set $491=$8739

proc printf=$8430(byte array a, card b, card c,
                  card d, card e, card f)
proc printe=$822C(byte array a)

proc main()
   printf("%S%E", "Test")
   printe("Runtime")
   do od

Kompilujemy i zapisujemy np. jako TESTRT.OBX
Potem sklejamy cat RUNTIME.OBX TESTRT.OBX > TESTRT.XEX

Dzięki temu każdy z dołączanych kawałków binarnych będzie mniejszy, a dodatkowo kompilacja będzie trwać krócej, bo RT kompilowane będzie tylko raz. Szkoda, że kompilatora uruchomionego w emulatorze nie da się odpalać w skrypcie. Można by do tego napisać makefile albo skrypt w bashu…

Action! Kompilacja do pliku.

CMPTODSK.ACT z Action! wiki ma jeden mały błąd w linii

IF c>'Z THEN c = c & $5F FI

a brakujący include BLKIO.ACT za niedługo może być trudny do znalezienia. Wrzucam tu dla porządku, bo kompilacja do pliku to przydatna rzecz.

MODULE ; CMPTODSK.ACT
; Copyright (c) 1983
; by Action Computer Services
; All Rights Reserved
; version 1.0
; last modified October 22, 1984
; Compile to disk for ACTION!
; compiler. Note that all ARRAY
; declarations that generate storage
; must be before the first procedure
; declaration or else the address of
; the storage will not be setup
; correctly (all dimensioned ARRAYs
; which are not assigned an initial
; value except BYTE/CHAR arrays of
; size 256 or less). Local ARRAY
; declarations in the main PROC (last
; procedure in program) are also
; allowed. Note: there must be at
; least one PROC/FUNC in program.

; Output file name will be same name
; as program being compiled with
; extention .OBJ

; IF AN ERROR OCCURS DURING
; COMPILATION, YOU SHOULD USE
; "/" to close all open files:
; >/
; change dev in SPLEnd below to direct
; output to printer.

DEFINE STRING = "CHAR ARRAY"
DEFINE JMP = "$4C" ; JMP addr16
TYPE INSTR=[BYTE op CARD addr]
INSTR Segvec=$4C6
INSTR SPLvec=$4DD
INSTR MonCmd=$4FB
INSTR OldMon
BYTE oldDevice, curBank=$4C9
BYTE pf, Zop=$8A, tZop, dev
CARD curproc=$8E, code=$E
CARD codeBase=$491, codeSize=$493
CARD codeOff=$B5
CARD globals, gsize
CARD totalSize, codeStart
CHAR ARRAY cmdLine(0)=$590
BYTE ARRAY bank(0)=$D500
BYTE ARRAY zpage(32), temps(16)

PROC InitMon()
 ; add "/" command to monitor which
 ; closes channels 1-5 and warm
 ; starts cartridge.
 CHAR cmdchar=$591
 BYTE i, WARMST=$8
 DEFINE JMPI="$6C"
 ; make sure right command
 IF cmdchar#'/ THEN [JMP OldMon] FI
 bank(0) = 0 ; init library routines
 FOR i = 1 TO 5 DO
  Close(i)
 OD
 WARMST = 1
 [JMPI $BFFA] ; warm start cart.

INCLUDE "H1:BLKIO.ACT"

PROC Save()
 ; save state of variables used by
 ; both compiler and library routines
 bank(0) = 0 ; init library routines
 tZop = Zop
 MoveBlock(zpage, $B0, $1B) ; to $CA
 MoveBlock(temps, $5F0, 16)
RETURN

PROC Restore()
 ; restore state of variables used by
 ; both compiler and library routines
 CARD tcodeOff
 Zop = tZop
 tcodeOff = codeOff
 MoveBlock($B0, zpage, $1B) ; to $CA
 MoveBlock($5F0, temps, 16)
 codeOff = tcodeOff
 bank(curBank) = 0
RETURN

PROC WriteHdr()
 PutCD(5, $FFFF)
 PutCD(5, codeStart)
 PutCD(5, codeStart+totalSize-1)
 WriteBlock(5, globals, gsize)
RETURN

PROC WriteCode()
 codeSize = code - codeBase
 PrintD(dev, curproc)
 PrintD(dev, ": ")
 PrintCDE(dev, codeSize)
 totalSize = totalSize + codeSize
 WriteBlock(5, codeBase, codeSize)
 code = codeBase
 codeOff = codeOff + codeSize
RETURN

PROC SegEnd()
 Save()
 IF pf THEN ; print locals
  WriteCode()
 ELSE
  pf = 1
  globals = codeBase
  gsize = code - codeBase
  codeBase = code
  totalSize = gsize
  codeStart = globals + codeOff
  WriteHdr()
 FI
 Restore()
RETURN

PROC SPL() ; dummy proc for call below

PROC SPLEnd()
 CHAR c
 BYTE nxttoken=$D3, i, n, buf=$9B^
 CARD nxtaddr=$C9, start=$2E2
 STRING inbuf(0)=$5C8, name
 STRING out(17)
 DEFINE PLA = "$68",
 STA = "$8D"
 Save()
 dev = 0
 ; to get output to printer:
 ; dev = 4
 ; Close(4) Open(4, "P:", 8, 0)
 ; get output name
 IF nxttoken=30 THEN ; command line
  name = nxtaddr
 ELSE ; editor buffer
  name = inbuf
 FI
 ; see if device needed
 n = 0
 IF name(2)#': AND name(3)#': THEN
  out(1) = 'D out(2) = ': n = 2
 FI
 ; get name without extension
 FOR i = 1 TO name(0) DO
  c = name(i)
  IF c='. THEN EXIT FI
  IF c>'Z THEN c = c & $5F FI
  out(i+n) = c
 OD
 ; add extension
 out(i+n) = '.
 out(i+n+1) = 'O
 out(i+n+2) = 'B
 out(i+n+3) = 'J
 out(0) = i + n + 3
 PutE()
 Print("output file is ")
 PrintE(out)
 PutE()
 Close(5) Open(5, out, 8, 0)
 buf = 0 ; clear buf used by Open
 pf = 0 ; no proc decl yet
 ; JSR for return so that we come
 ; back here after compilation
 [
  PLA
  STA SPL+1
  PLA
  STA SPL+2
 ]
 SPL = SPL + 1 ; get right address
 Restore()
 SPL()
 Save()
 ; ignore space for arrays
 code = codeBase + codeSize
 WriteCode()
 PutCD(5, $2E2)
 PutCD(5, $2E3)
 PutCD(5, start)
 Close(5)
 Open(5, out, $C, 0)
 WriteHdr()
 Close(5)
 PutDE(dev)
 PrintCD(dev, totalSize)
 PrintDE(dev, " bytes of code")
 Restore()
 codeOff = 0
RETURN

; only code generated before Init is
; allocated space. Init will be
; garbage collected (well kind of).

PROC Init()
 CARD codeBlock, bsize, csize, nBlock
 CARD POINTER cur, next
 ; link in our routines
 Segvec.op = JMP
 Segvec.addr = SegEnd
 SPLvec.op = JMP
 SPLvec.addr = SPLEnd
 OldMon.op = MonCmd.op
 OldMon.addr = MonCmd.addr
 MonCmd.op = JMP
 MonCmd.addr = InitMon
 ; allocate our routine so it won't
 ; go away.
 codeBlock = codeBase - 4
 next = $80 ; AFbase
 DO
  cur = next
  next = next^
 UNTIL next=0 OR next=codeBlock OD
 IF next=0 THEN
  PutE() Put($FD)
  PrintE("I can't allocate space for your code")
  PrintE("You better Boot and try again!")
  RETURN
 FI
 ; assume we can split block
 csize = @codeBlock-codeBlock
 nBlock = next^
 bsize = next(1) - csize
 next = @codeBlock
 cur^ = next
 next^ = nBlock
 next(1) = bsize
 codeBase = next + 4
RETURN
MODULE ; BLKIO.ACT
; Copyright (c) 1983, 1984, 1985
; by Action Computer Services (ACS)
; This software may be incorporated in
; other software packages providing
; that this copyright notice is also
; incorporated as well.
; version 1.1
; last modified May 8, 1985
BYTE CIO_status
CHAR FUNC CIO=*(BYTE dev, CARD addr,
 size, BYTE cmd, aux1, aux2)
; see hardware manual for description
; of CIOV.
; IOCB# = dev
; ICCOM = cmd
; ICBA = addr
; ICBL = size
; ICAX1 = aux1
; ICAX2 = aux2
; ICAX1 and ICAX2 are not set if aux1=0
; The first byte of addr is passed to
; CIO in the A register. The status
; on return from CIO is stored in
; CIO_status. If status=$88 then
; EOF(dev) is set to a non-zero value.
; No other error checking is performed
; and the result of the CIOV call is
; returned as the result of this FUNC.
[$29$F$85$A0$86$A1$A$A$A$A$AA$A5$A5
$9D$342$A5$A3$9D$348$A5$A4$9D$349
$A5$A6$F0$8$9D$34A$A5$A7$9D$34B$98
$9D$345$A5$A1$9D$344$20$E456
$8C CIO_status$C0$88$D0$6$98$A4$A0
$99 EOF$A085$60]
CARD FUNC ReadBlock=*(BYTE dev,
 CARD addr, size)
; Reads size bytes from dev into addr.
; Returns number of bytes read (may
; be < size if EOF). Set EOF flag if
; EOF is encountered. Status is
; saved in CIO_status.
[$48$A9$7$85$A5$A9$0$85$A6$A5$A3$5$A4
$D0$6$85$A0$85$A1$68$60$68$20 CIO
$BD$348$85$A0$BD$349$85$A1$60]
PROC WriteBlock=*(BYTE dev,
 CARD addr, size)
; Writes size bytes from addr to dev.
; Status is saved in CIO_status.
[$48$A9$B$85$A5$A9$0$85$A6$A5$A3$5$A4
$D0$2$68$60$68$4C CIO]
PROC PutCD=*(BYTE chan, CARD n)
 BYTE c=$AA, lo=$AB, hi=$AC
; save args
 [
 $85 c
 $86 lo
 $84 hi
 ]
; PutD(c, lo)
; PutD(c, hi)
 CIO(c,lo,0,11,0)
 CIO(c,hi,0,11,0)
RETURN
CARD FUNC GetCD(BYTE chan)
 CARD out
 BYTE lo=out, hi=out+1
; lo = GetD(chan)
; hi = GetD(chan)
 lo = CIO(chan,0,0,7,0)
 hi = CIO(chan,0,0,7,0)
RETURN(out)
MODULE ; for user

Działa to podobnie jak symbol table lister:

  • uruchamiamy CMPTODSK.ACT w monitorze (bez ładowania do edytora):
    Ctrl+Shift+M, R „H1:CMPTODSK.ACT”
  • wychodzimy do edytora, ładujemy nasz program Ctrl+Shift+R, H1:PLIK.ACT
  • kompilujemy Ctrl+Shift+M, C
  • skompilowany plik binarny zostaje zapisany pod nazwą H1:PLIK.OBJ

Teoretycznie to kolejny sposób na małą ilość pamięci i kompilowanie większych kawałków kodu Action!

Dyskietka auto-bootująca.

Wymyśliłem sobie, żeby z poziomu modułu głównego programu dogrywać kolejne fragmenty programu lub wręcz uruchamiać kolejne binarki. Wszystko super, loader z poprzedniego wpisu po wskazaniu odpowiedniego initad działał, ale zacząłem się zastanawiać jak zrobić obraz atr, w którym taki loader będzie automatycznie startował. Znalazłem sporo programów które to umożliwiają, żaden nie zadziałał. Nawet picoboot poległ na pliku złożonym z kilku sklejonych catem segmentów.

Rozwiązanie okazuje się banalnie proste.

Sparta Dos 3.2:

  • tworzymy pusty obraz dyskietki w emulatorze
  • inicjujemy DOS za pomocą XINIT
  • kopiujemy loader.xex na tą dyskietkę jako AUTORUN.SYS
  • kopiujemy xexy doładowywane przez loader/autorun.sys

Uruchomienie Sparty jest jednak dość powolne, a idealnie sprawdził się tutaj TurboDos XE:

  • tworzymy pusty obraz dyskietki w emulatorze
  • formatujemy np: FMD 2:  tu jest ściągawka
  • przechodzimy na 2: i kopiujemy pliki systemowe za pomocą INI
  • kopiujemy loader jako AUTORUN.SYS
  • kopiujemy programy ładowane przez loader

Co ciekawe taki autorun startuje naprawdę błyskawicznie.

Loader do linkowanych binarek Action!

Loader do plików binarnych z Action! Wiki nie działa z binarkami z kompilatora Action! łączonymi cat lub type. Sprawdziłem w dis6502 i wynika to z tego, że loader ładuje segmenty do pierwszego wystąpienia INITAD ($02E2), a w naszym pliku binarnym utworzonym przez cat lub type, INITAD jest definiowany kilka razy (w każdej z binarek składowych). W poprzednim przykładzie z FINAL.XEX interesuje nas drugi INITAD, przekazujemy więc 2 jako parametr procedury Load. BTW nie wiem czy da się wyłączyć w kompilatorze dodawanie INITAD, ale loader może sobie to zliczać.

Poniżej lekko zmieniona wersja loadera, która czyta bloki do wartości parametru initadn.


MODULE ;LOAD.ACT
BYTE CIO_status
CARD start, len

CHAR FUNC CIO=*(BYTE dev, CARD addr,size, BYTE cmd,aux1,aux2)
[$29$F$85$A0$86$A1$A$A$A$A$AA$A5$A5$9D$342$A5$A3$9D$348$A5$A4$9D$349
$A5$A6$F0$8$9D$34A$A5$A7$9D$34B$98$9D$345$A5$A1$9D$344$20$E456
$8C CIO_status$C0$88$D0$6$98$A4$A0$99 EOF$60]

CARD FUNC ReadBlock=*(BYTE dev, CARD addr, size)
[$48$A9$7$85$A5$A9$0$85$A6$A5$A3$5$A4$D0$6$85$A0$85$A1$68$60$68
$20 CIO$BD$348$85$A0$BD$349$85$A1$60]

CARD FUNC GetOne()
  BYTE cLow
  CARD cHigh
  DO
    cLow=GetD(1) cHigh=GetD(1)
    cHigh== LSH 8 % cLow
  UNTIL cHigh#$FFFF OD
RETURN(cHigh)

PROC GetAddrs=*()
  start=GetOne()
  len=GetOne()-start+1
RETURN

PROC Load(CHAR ARRAY filespec, BYTE initadn)
  CARD INITAD=$2E2
  INT tmp=[0]
  Close(1)
  Open(1,filespec,4,0)
  WHILE tmp#initadn DO
    IF start=$2E2 THEN
      tmp==+1
    FI
    GetAddrs()
    ReadBlock(1,start,len)
  OD
  Close(1)
  [$6C INITAD]
RETURN

PROC MAIN()
 Load("H1:FINAL.XEX", 2)
RETURN

Mała uwaga: loader kompiluje się z alternatywnym runtime Jeffa Reistera. Z oryginalnym z OSS wywala error 11.