home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / grafik / tiff / vgasave.asm < prev    next >
Encoding:
Assembly Source File  |  1990-09-18  |  26.0 KB  |  668 lines

  1. ;****************************************
  2. ;* VGASAVE.ASM
  3. ;*
  4. ;* Syntax:
  5. ;* VGASAVE [dateiname] [/Tx]   :VGASAVE installieren, angegebenen
  6. ;*                Dateinamen (Default: VGA_BILD.001) beim
  7. ;*                Speichern benutzen, Aktivierung über
  8. ;*                <ALT> + Taste x (default: S)
  9. ;* VGASAVE -               :VGASAVE deinstallieren
  10. ;* VGASAVE ?               :Information anzeigen, ob VGASAVE bereits
  11. ;*                im Speicher installiert ist, falls ja:
  12. ;*                Dateinamen und Hotkey anzeigen
  13. ;*
  14. ;****************************************
  15.  
  16. ;*** Makro zum Lesen von Zeichen aus Parameterzeile
  17.  
  18. getpar  macro
  19.         local mac002         ;Lokale Sprungmarke definieren
  20.  
  21.         mov  al,ds:anzpar    ;Anzahl Zeichen in Parameterzeile holen
  22.         mov  ah,00           ;auf 16 Bit bringen
  23.         cmp  si,ax           ;Zeiger in Parameterzeile mit Anzahl vgl.
  24.         stc                  ;Ende signalisieren
  25.         je   mac002          ;Ende erreicht
  26.         mov  al,parzeil[si]  ;noch kein Ende: Zeichen nach al holen
  27.         inc  si              ;Zeiger in Parameterzeile erhöhen
  28.         clc                  ;Carry-Flag löschen -> Ende nicht erreicht
  29. mac002:
  30.         endm                 ;Ende des Makros
  31.  
  32. defkey    equ  1f00h         ;erweiterter Tastaur-Code Default-Hotkey
  33. defkeyc    equ  "S"         ;Default-Hotkey ist <ALT> + S
  34. anzpar  equ  80h             ;PSP Offset 80h: Länge der Parameterzeile
  35. parzeil equ  82h             ;PSP Offset 82h: Parameterzeile
  36.         ;                     (eigentlich 81h, dort aber Leerzeichen)
  37. video   equ 0a000h           ;Segment-Adresse des Video-RAMs
  38.  
  39. code    segment para 'CODE'  ;Code-Segment (und Daten- u. Stacksegment)
  40.  
  41.         org 100h             ;256 Byte für PSP reservieren
  42.  
  43.         assume cs:code, ds:code, es:code, ss:code
  44.  
  45. start:  jmp  iniproc         ;Sprung zur Initialisierungsroutine
  46.  
  47. altint  equ this dword       ;Far-Adresse des alten Interrupt-Vektors
  48. altinto dw (?)               ;deren Offset-Adresse
  49. altints dw (?)               ;deren Segment-Adresse
  50. handle  dw (?)               ;DOS-Datei-Handle
  51. pag     db (?)               ;selektiertes RAM-Segment der VGA-Karte
  52. off     dw (?)               ;Offset innerhalb RAM-Segment der VGA-Karte
  53. count   dw (?)               ;Zähler für Byte-Wiederholungen
  54. wdhsatz equ this byte        ;Datensatz zum Speichern einer Wiederholung
  55. kennung db (?)               ;  dessen einleitende Kennung
  56.         dw (?)               ;  Anzahl der Byte-Wiederholungen
  57.         db (?)               ;  zu wiederholendes Byte
  58. lesetab db 2 dup (?)         ;FIFO-Puffer für zuletzt eingelesene 2 Bytes
  59. paltab  db 3*256 dup (?)     ;Puffer für Auslesen der Paletten-Register
  60. anz_l   dw 256 dup (?)       ;Tabelle für Byte-Häufigkeiten -> Low-Word
  61. anz_h   dw 256 dup (?)       ;dto. -> High-Word
  62.  
  63. ;*** neue Interrupt-Routine für Interrupt 09h
  64. ;*** nach Aufruf des alten Interrupts wird auf Tastendruck von
  65. ;*** ALT-S getestet und ggf. das aktuelle VGA-Bild komprimiert und
  66. ;*** gespeichert
  67.  
  68. neuint  proc far             ;neue Interrupt-Routine für Interrupt 09h
  69.  
  70.         jmp  short skip_id   ;Kennung überspringen
  71.  
  72. ;*** Kennung der Interrupt-Routine
  73. ;*** Mit Hilfe dieser Kennung kann bei wiederholtem Aufruf von VGASAVE
  74. ;*** festgestellt werden, ob VGASAVE bereits installiert ist. Ggf. wird
  75. ;*** VGASAVE dann deinstalliert.
  76.  
  77.         db   "VgaSave!"
  78.  
  79. ;*** zuerst noch alte Interrupt-Routine ausführen
  80.  
  81. skip_id:pushf                ;Interrupt-Aufruf simulieren
  82.         call cs:[altint]     ;Sprung in alte Interrupt-Routine
  83.  
  84.         cli                  ;maskierbare Interrupts sperren
  85.  
  86.         push ax              ;alle Register sichern
  87.         push bx
  88.         push cx
  89.         push dx
  90.         push di
  91.         push si
  92.         push bp
  93.         push ds
  94.         push es
  95.  
  96. ;*** Tastatureingabe testen
  97.  
  98.         mov  ah,1            ;Tastatur-Interrupt, Fkt. 1:
  99.         int  16h             ;Zeichen in Tastaturpuffer vorhanden?
  100.  
  101.         je   quit            ;kein Zeichen vorhanden
  102.  
  103.         cmp  ax,1f00h        ;ALT-S gedrückt ?
  104.         jne  quit            ;nein
  105.  
  106.         mov  ah,0            ;Fkt. 0: Zeichen auslesen
  107.         int  16h             ;ALT-S aus Tastaturpuffer entfernen
  108.  
  109. ;*** auf korrekten Videomodus testen
  110.  
  111.         mov  ah,0fh          ;Video-Modus überprüfen
  112.         int  10h             ;Fkt. 0fh: Video-Informationen liefern
  113.         cmp  al,30h          ;800x600 Punkte, 256 aus 256K Farben ?
  114.         je   ok2             ;ja, in Ordnung
  115.         jmp  modeerr         ;nein, Modus-Fehler
  116.  
  117. ;*** Aktion einleiten
  118.  
  119. ok2:    mov  dx,9121         ;Tonfolge für Beginn der Aktion
  120.         call sound
  121.         mov  dx,6087
  122.         call sound
  123.         mov  dx,4560
  124.         call sound
  125.  
  126.         call aktion          ;Bild auslesen, packen, abspeichern
  127.  
  128.         mov  dx,4560         ;Tonfolge Ende der Aktion
  129.         call sound
  130.         mov  dx,6087
  131.         call sound
  132.         mov  dx,9121
  133.         call sound
  134.  
  135. ;*** Interrupt beenden
  136.  
  137. quit:   pop  es              ;gesicherte Register zurückholen
  138.         pop  ds
  139.         pop  bp
  140.         pop  si
  141.         pop  di
  142.         pop  dx
  143.         pop  cx
  144.         pop  bx
  145.         pop  ax
  146.  
  147.         iret                 ;Interrupt beenden
  148.  
  149. ;*** akustische Fehlermeldungen
  150.  
  151. openerr:mov  dx,20000        ;Fehler beim Öffnen der Datei
  152.         call sound           ;tiefen Ton erzeugen
  153.  
  154. modeerr:mov  dx,20000        ;Fehler durch falschen Video-Modus
  155.         call sound           ;tiefen Ton erzeugen
  156.         jmp  short quit      ;Interrupt beenden
  157.  
  158. neuint  endp                 ;Ende der Interrupt-Routine
  159.  
  160. ;*** Prozedur für Tonerzeugung
  161. ;*** Mittels dem Zeitgeber 8253 wird ein periodisches Signal erzeugt.
  162. ;*** Die Frequenz wird durch den übergebenen Wert im DX-Register
  163. ;*** bestimmt (Formel: Frequenz [Hz] = 1.193.180 : Wert). Die Tonlänge
  164. ;*** ist konstant und wird durch eine simple Verzögerungsschleife
  165. ;*** zwischen Ein- und Ausschalten des Lautsprechers realisiert.
  166.  
  167. sound   proc near
  168.  
  169. ;*** Tonerzeugung einleiten
  170.  
  171.         mov  al,182          ;Wert 182 auf Port 43h:
  172.         out  43h,al          ;Zeitgeber aktivieren
  173.         mov  ax,dx           ;Frequenz-Parameter nach AX bringen
  174.         out  42h,al          ;Low-Byte an Zeitgeber senden
  175.         mov  al,ah           ;High-Byte nach AL
  176.         out  42h,al          ;High-Byte an Zeitgeber senden,
  177.         ;                     Signal wird nun erzeugt
  178.         in   al,61h          ;Port für Lautsprechersteuerung lesen
  179.         or   al,11b          ;Signal des Zeitgebers an Membran leiten
  180.         out  61h,al          ;Lautsprecher einschalten
  181.  
  182. ;*** Warteschleife
  183.  
  184.         mov  bx,0            ;Verzögerungszähler (low) initialisieren
  185.         mov  cx,8            ;dto. (high)
  186. delay:  dec  bx              ;inneren Zähler bis 0 herunterzählen
  187.         jne  delay
  188.         dec  cx              ;äußeren Zähler bis 0 herunterzählen
  189.         jne  delay
  190.  
  191. ;*** Lautsprecher ausschalten
  192.  
  193.         in   al,61h          ;Port für Lautsprechersteuerung lesen
  194.         and  al,11111100b    ;Bit 0 und löschen
  195.         out  61h,al          ;Lautsprecher ausschalten
  196.         ret                  ;Rücksprung ins Hauptprogramm
  197.  
  198. sound   endp                 ;Ende der Prozedur
  199.  
  200. ;*** Prozedur zum Auslesen des Video-RAMs, Komprimieren der Daten und
  201. ;*** anschließendem Abspeichern.
  202. ;*** Da bei Bildern benachbarte Punkte häufig die gleiche Farbe auf-
  203. ;*** weisen, arbeitet der eingesetzte Komprimier-Algorithmus trotz
  204. ;*** seiner Einfachheit recht effektiv: Bei aufeinanderfolgenden
  205. ;*** gleichen Bytes werden diese als Wiederholung abgespeichert in der
  206. ;*** Form [Kennung],[Anzahl der Wiederholungen],[Bytewert]. Der
  207. ;*** Dekomprimieralgorithmus erkennt eine Wiederholung an der Kennung,
  208. ;*** aus diesem Grunde muß ein im Bild auftretender Byte-Wert, der mit
  209. ;*** dem Kennungsbyte identisch ist, ebenfalls als Wiederholung kodiert
  210. ;*** werden. Um diesen Nachteil zu minimieren, wird zu Beginn des Kom-
  211. ;*** primier-Vorgangs das Bild untersucht und das am seltensten auf-
  212. ;*** tretende Byte als Kennung benutzt.
  213.  
  214. aktion  proc near
  215.  
  216. ;*** Datensegment-Register auf Codesegment setzen, da sich Daten im
  217. ;*** Codesegment befinden
  218.  
  219.         push cs              ;Codesegment sichern
  220.         pop  ds              ;und als Datensegment zurückholen
  221.         assume ds:code       ;Assembler mitteilen, daß sich nun das
  222.         ;                     Datensegment mit Codesegment deckt
  223.  
  224. ;*** VGA-Bild untersuchen, um am seltensten auftretendes Byte ermitteln
  225.  
  226. ;*** Häufigkeits-Tabelle initialisieren
  227.  
  228.         mov  di,510          ;256 Wörter => Offset 0 - 510
  229. null:   mov  anz_l[di],0     ;Low-Word mit 0 initialisieren
  230.         mov  anz_h[di],0     ;dto. High-Word
  231.         sub  di,2            ;Zeiger auf nächstes Wort
  232.         jns  null            ;Null noch nicht unterschritten => weiter
  233.  
  234. ;*** VGA-RAM durchsuchen und Häufigkeiten ermitteln
  235.  
  236. ;*** Initialisierungen
  237.  
  238.         mov  pag,0           ;Wert für RAM-Segment der VGA-Karte
  239.         mov  si,0            ;Offset innerhalb des VGA-RAM-Segments
  240.         mov  ax,video        ;Adresse des VGA-RAM-Segments
  241.         mov  es,ax           ;ins ES-Register
  242.  
  243. ;*** VGA-RAM-Segment selektieren
  244.  
  245. s1:     mov  dx,3cdh         ;3cdh: GDC-Segment-Select-Register
  246.         in   al,dx           ;SSR-Register auslesen
  247.         and  al,11000111b    ;Bits 3-5 löschen (Read-Segment-Pointer)
  248.         mov  bl,pag          ;Wert für VGA-RAM-Segment
  249.         mov  cl,3            ;Anzahl der Links-Shifts
  250.         shl  bl,cl           ;Wert an die richtige Bit-Position schieben
  251.         or   al,bl           ;Bits 3-5 auf Segment-Nummer setzen
  252.         out  dx,al           ;zurückschreiben
  253.  
  254. ;*** VGA-RAM-Segment auslesen und Bytes zählen
  255.  
  256. w2:     mov  al,es:[si]      ;aktuelles Byte auslesen
  257.         mov  ah,0            ;auf 16 Bit bringen
  258.         shl  ax,1            ;Multiplikation mit 2, da Wort-Zugriff
  259.         mov  di,ax           ;Ergebnis als Index in Tabelle nach DI
  260.         inc  anz_l[di]       ;Eintrag in Tabelle erhöhen
  261.         jnz  w1              ;kein Überlauf, ok
  262.         inc  anz_h[di]       ;sonst noch Übertrag berücksichtigen
  263. w1:     inc  si              ;Offset in VGA-RAM-Segment erhöhen
  264.         jnz  w2              ;noch nicht am Ende, dann weiter
  265.         inc  pag             ;sonst Segment-Nummer erhöhen
  266.         cmp  pag,8           ;mit Endwert vergleichen
  267.         jb   s1              ;noch kleiner, dann weiter
  268.  
  269. ;*** Untersuchung des VGA-RAMs abgeschlossen, kleinsten Wert suchen
  270. ;*** AX/BX enthalten bisher kleinsten Wert, CX/DX enthalten laufende
  271. ;*** Tabellen-Werte für Vergleich mit AX/BX
  272.  
  273. ;*** Initialisierungen
  274.  
  275.         mov  ax,anz_l[0]     ;AX mit erstem Tabellenwert (low) besetzen
  276.         mov  bx,anz_h[0]     ;dto. BX (high)
  277.         mov  di,0            ;Index für bisher kleinsten Wert
  278.         mov  si,2            ;laufender Index
  279.  
  280. ;*** Vergleich von laufendem Element mit AX/BX
  281.  
  282. vergl:  mov  cx,anz_l[si]    ;laufendes Low-Word nach CX
  283.         mov  dx,anz_h[si]    ;laufendes High-Word nach DX
  284.         cmp  ax,cx           ;32 Bit-Vergleich AX/BX <-> CX/DX
  285.         sbb  bx,dx
  286.         jc   kleiner         ;AX/BX immer noch kleiner als CX/DX
  287.  
  288. ;*** neuen kleinsten Wert gefunden
  289.  
  290.         mov  ax,cx           ;laufendes Low-Word nach AX
  291.         mov  bx,dx           ;laufendes High-Word nach BX
  292.         mov  di,si           ;laufenden Index nach SI
  293.  
  294. ;*** Schleifenende
  295.  
  296. kleiner:add  si,2            ;laufenden Index erhöhen
  297.         cmp  si,512          ;mit Ende vergleichen
  298.         jne  vergl           ;noch nicht am Ende, dann weiter suchen
  299.  
  300. ;*** kleinster Wert ist ermittelt
  301.  
  302.         mov  ax,di           ;Index für kleinsten Wert nach AX
  303.         shr  ax,1            ;entsprechendes Byte = Index / 2
  304.         mov  kennung,al      ;als Kennung ablegen
  305.  
  306. ;*** Datei öffnen
  307.  
  308.         mov  ah,3ch          ;DOS-Fkt. 3ch: Datei erstellen
  309.         mov  cx,0            ;normales Datei-Attribut
  310.         mov  dx,offset datname;Adresse des Dateinamens
  311.         int  21h             ;Öffnen der Datei
  312.         jnc  ok1             ;kein Fehler aufgetreten, dann weiter
  313.         jmp  openerr         ;sonst Fehlermeldung
  314.  
  315. ok1:    mov  handle,ax       ;DOS-Datei-Handle merken
  316.  
  317. ;*** Paletten-Register speichern
  318.  
  319. ;*** Extrasegment-Register auf Datensegment setzen, da sich Puffer
  320. ;*** für Palettendaten im Datensegment befindet
  321.  
  322.         push ds              ;Datensegment sichern
  323.         pop  es              ;und als Extrasegment zurückholen
  324.  
  325.         mov  ah,10h          ;Fkt. 10h: Paletten-Register bearbeiten
  326.         mov  al,17h          ;Sub-Fkt. 17h: Palettenregister-Block lesen
  327.         mov  dx,offset paltab;ES:DX = Zeiger auf Datenpuffer
  328.         mov  bx,0            ;bei Paletten-Register 0 beginnen
  329.         mov  cx,256          ;(alle) 256 Register auslesen
  330.         int  10h             ;ausführen
  331.  
  332.         mov  ah,40h          ;DOS-Fkt. 40h: Datei beschreiben
  333.         mov  bx,handle       ;Handle der Datei setzen
  334.         mov  cx,3*256        ;Anzahl Bytes (1 Register = 3 Bytes [RGB])
  335.         mov  dx,offset paltab;Adresse des Datenpuffers
  336.         int  21h             ;Speichern ausführen
  337.  
  338. ;*** Color-Page-Informationen speichern
  339.  
  340.         mov  ah,10h          ;Fkt. 10h: Paletten-Register bearbeiten
  341.         mov  al,1ah          ;Sub-Fkt. 1ah: Color-Page-Status liefern
  342.         int  10h             ;ausführen
  343.         mov  paltab[0],bl    ;aktuellen Paging-Mode sichern
  344.         mov  paltab[1],bh    ;aktuelle Color-Page sichern
  345.  
  346.         mov  ah,40h          ;DOS-Fkt. 40h: Datei beschreiben
  347.         mov  bx,handle       ;Handle der Datei setzen
  348.         mov  cx,2            ;Anzahl Bytes
  349.         mov  dx,offset paltab;Adresse des Datenpuffers
  350.         int  21h             ;Speichern ausführen
  351.  
  352. ;*** bei diesem Bild verwendete Wiederholungs-Kennung speichern
  353.  
  354.         mov  ah,40h          ;DOS-Fkt. 40h: Datei beschreiben
  355.         mov  bx,handle       ;Handle der Datei setzen
  356.         mov  cx,1            ;Anzahl Bytes
  357.         mov  dx,offset kennung;Adresse des Datenpuffers
  358.         int  21h             ;Speichern ausführen
  359.  
  360. ;*** Video-RAM auslesen, packen, speichern
  361.  
  362. ;*** Initialisierungen
  363.  
  364.         mov  pag,0           ;Wert für VGA-RAM-Segment
  365.         mov  off,0           ;Offset innerhalb VGA-RAM-Segment
  366.         mov  count,0         ;Zähler für Byte-Wiederholungen
  367.         mov  si,0            ;Zeiger für FIFO-Lesepuffer
  368.         mov  ax,video        ;Extrasegment auf VGA-RAM-Segment
  369.         mov  es,ax
  370.  
  371. ;*** aktuelles VGA-RAM-Segment selektieren
  372.  
  373. seite:  mov  dx,3cdh         ;3cdh: GDC-Segment-Select-Register
  374.         in   al,dx           ;SSR-Register auslesen
  375.         and  al,11000111b    ;Bits 3-5 löschen (Read-Segment-Pointer)
  376.         mov  bl,pag          ;Wert für VGA-RAM-Segment
  377.         mov  cl,3            ;Anzahl der Links-Shifts
  378.         shl  bl,cl           ;Wert an die richtige Bit-Position schieben
  379.         or   al,bl           ;Bits 3-5 auf Segment-Nummer setzen
  380.         out  dx,al           ;zurückschreiben
  381.  
  382. ;*** aktuelles Byte auslesen
  383.  
  384. readram:mov  di,off          ;Offset in aktuelles VGA-RAM-Segment
  385.         mov  al,es:[di]      ;Byte aus Video-Ram auslesen
  386.         mov  lesetab[si],al  ;FIFO-Puffer füllen
  387.  
  388. ;*** Sonderfall Startzustand behandeln
  389.  
  390.         cmp  si,0            ;Zeiger in FIFO-Puffer = 0 ?
  391.         je   plus            ;ja -> Startzustand, erstes Byte gelesen
  392.  
  393. ;*** auf Byte-Wiederholung testen
  394.  
  395.         cmp  al,lesetab[0]   ;Vergleich mit erstem Byte aus FIFO-Puffer
  396.         je   plus            ;Gleichheit: Wiederholungen zählen
  397.  
  398. ;*** zum Vorgänger unterschiedliches Byte
  399.  
  400.         cmp  count,4         ;Wiederholungs-Zähler prüfen
  401.         jnb  save2           ;bei mehr als 3 Wiederholungen komprimieren
  402.  
  403. ;*** Sonderfall: gelesenes Byte = Kennungsbyte
  404.  
  405.         mov  al,kennung      ;Kennungsbyte holen
  406.         cmp  al,lesetab[0]   ;mit zu speicherndem Zeichen vergleichen
  407.         je   save2           ;bei Gleichheit als Wiederholung speichern
  408.  
  409. ;*** Speichern von 1-3 Zeichen
  410.  
  411.         mov  di,count        ;Wiederholungszähler nach DI
  412.         dec  di              ;um 1 vermindern, da Schleifenende bei -1
  413.  
  414. ;*** Speichern im Normalfall (keine Wiederholung) 1 - 3 mal
  415.  
  416. save1:  mov  ah,40h          ;DOS-Fkt. 40h: Datei beschreiben
  417.         mov  bx,handle       ;Handle der Datei setzen
  418.         mov  cx,1            ;Anzahl der Zeichen = 1 (aber DI-Schleife)
  419.         mov  dx,offset lesetab;Adresse des Datenbereichs
  420.         int  21h             ;Speichern ausführen
  421.         dec  di              ;Zähler vermindern
  422.         cmp  di,0ffffh       ;auf Ende testen
  423.         jne  save1           ;falls Ende nicht erreicht: weitermachen
  424.  
  425. ;*** FIFO-Stack shiften
  426.  
  427. rueck:  mov  al,lesetab[1]   ;2. Byte holen
  428.         mov  lesetab[0],al   ;an 1. Stelle schreiben
  429.         mov  si,1            ;Zeiger auf 2. Byte setzen
  430.         mov  count,1         ;Wiederholungszähler initialisieren
  431.         jmp  short plus2     ;Lesezeiger auf nächstes Zeichen
  432.  
  433. ;*** Wiederholungen zählen
  434.  
  435. plus:   mov  si,1            ;FIFO-Zeiger auf 2. Byte
  436.         inc  count           ;Wiederholungszähler erhöhen
  437.         cmp  count,0ffffh    ;mit Maximalwert vergleichen
  438.         jne  plus2           ;kein Überlauf, ok
  439.         call savewdh         ;sonst Zwischenspeichern
  440.         mov  count,0         ;Wiederholungszähler initialisieren
  441.         mov  si,0            ;FIFO-Zeiger auf 1. Byte (Startzustand)
  442.  
  443. ;*** Lesezeiger auf nächstes Byte in VGA-RAM
  444.  
  445. plus2:  inc  off             ;Offset in Video-RAM-Segment erhöhen
  446.         jnz  readram         ;Segment-Ende nicht erreicht, weiterlesen
  447.  
  448. ;*** Wert für nächstes VGA-RAM-Segment setzen
  449.  
  450.         inc  pag             ;VGA-RAM-Segment-Zähler erhöhen
  451.         cmp  pag,8           ;mit Endwert (+1) vergleichen
  452.         jnb  savrest         ;Ende erreicht, Rest speichern
  453.         jmp  seite           ;Ende nicht erreicht, Segment selektieren
  454.  
  455. ;*** Speichern einer Wiederholung, Komprimieren
  456.  
  457. save2:  call savewdh         ;Wiederholung speichern
  458.         jmp  short rueck     ;FIFO-Stack shiften, weiter einlesen
  459.  
  460. ;*** bei Erreichen RAM-Endes noch nicht verarbeiteten Rest abspeichern
  461.  
  462. ;*** auf Sonderfall prüfen: gelesenes Byte = Kennungsbyte
  463.  
  464. savrest:mov  al,lesetab[0]   ;letztes Zeichen holen
  465.         cmp  al,kennung      ;und mit Kennungs-Byte vergleichen
  466.         je   save4           ;Übereinstimmung: als Wiederholung speichern
  467.  
  468. ;*** bei mehr als 3 Wiederholungen als Wiederholung speichern
  469.  
  470.         cmp  count,4         ;Wiederholungszähler mit 4 vergleichen
  471.         jnb  save4           ;größer: als Wiederholung speichern
  472.  
  473. ;*** sonst als einzelne Bytes speichern
  474.  
  475.         mov  di,count        ;Anzahl der Bytes nach DI
  476.         jmp  short decdi     ;Einsprung in Schleife
  477.  
  478. ;*** Schleife für Speichern von einzelnen Rest-Bytes
  479.  
  480. save3:  mov  ah,40h          ;DOS-Fkt. 40h: Datei beschreiben
  481.         mov  bx,handle       ;Handle der Datei setzen
  482.         mov  cx,1            ;Anzahl der Zeichen
  483.         mov  dx,offset lesetab;Adresse des Datenpuffers
  484.         int  21h             ;Speichern ausführen
  485. decdi:  dec  di              ;Anzahl vermindern
  486.         jns  save3           ;Ende noch nicht erreicht, Schleife
  487.         jmp  short close     ;sonst Sprung zum Dateischließen
  488.  
  489. ;*** Rest als Wiederholung speichern
  490.  
  491. save4:  call savewdh         ;Wiederholung speichern
  492.           
  493. ;*** Datei schließen
  494.  
  495. close:  mov  ah,3eh          ;DOS-Fkt. 3eh: Datei schließen
  496.         mov  bx,handle       ;Handle der Datei setzen
  497.         int  21h             ;Schließen ausführen
  498.  
  499.         ret                  ;Rücksprung zum Hauptprogramm
  500.  
  501. aktion  endp                 ;Ende der Prozedur
  502.  
  503. ;*** Prozedur zum Speichern einer Wiederholung
  504.  
  505. savewdh proc near
  506.  
  507.         mov  ax,count        ;Anzahl der Wiederholungen nach AX
  508.         mov  wdhsatz[1],al   ;Low-Byte setzen
  509.         mov  wdhsatz[2],ah   ;High-Byte setzen
  510.         mov  al,lesetab[0]   ;zu wiederholendes Byte nach AL
  511.         mov  wdhsatz[3],al   ;setzen
  512.         mov  ah,40h          ;DOS-Fkt. 40h: Datei beschreiben
  513.         mov  bx,handle       ;Handle der Datei setzen
  514.         mov  cx,4            ;Anzahl der Zeichen
  515.         mov  dx,offset wdhsatz;Adresse des Datenpuffers
  516.         int  21h             ;Speichern durchführen
  517.  
  518.         ret                  ;zurück zum Hauptprogramm
  519.  
  520. savewdh endp                 ;Ende der Prozedur
  521.  
  522. datname db 80 dup (?)        ;Bereich für Dateinamen
  523.  
  524. defname db "vga_bild.bin"    ;Default-Dateiname
  525. defnam0 equ this byte        ;End-Adresse (+1) des Default-Dateinamens
  526.  
  527. residen equ this byte        ;bis hier bleibt's resident
  528.  
  529. inst_m  db 13,10,13,10       ;Text für Installationsmeldung
  530.         db "VGASAVE wurde installiert !",13,10
  531.         db 13,10
  532.         db "RAM-residentes Tool, um VGA-Bild (800x600 Punkte,",13,10
  533.         db "256 aus 256K Farben) zu komprimieren und auf",13,10
  534.         db "Diskette/Festplatte abzuspeichern.",13,10
  535.         db 13,10
  536.         db "Aufruf durch < ALT-S >.",13,10
  537.         db "Eine Tonfolge signalisiert daraufhin das Starten des",13,10
  538.         db "Prozesses, nach Beendigung erklingt eine weitere",13,10
  539.         db "Tonfolge.",13,10
  540.         db "Bei falschem Video-Modus erklingt 1 tiefer Ton, bei",13,10
  541.         db "Fehlern beim Öffnen der Datei erklingen 2 tiefe Töne.",13,10
  542.         db 13,10
  543.         db "Anzeige eines gespeicherten Bildes mit",13,10,13,10
  544.         db "    VGALOAD [Pfad] Dateiname [/A] [/E] [/Wnn]",13,10
  545.         db 13,10,"$"
  546.  
  547. meldaus db 13,10,13,10       ;Text für Deinstallationsmeldung
  548.         db "VGASAVE wurde deinstalliert !",13,10,13,10,"$"
  549.  
  550. ;*** Interrupt-Initialisierungs-Routine
  551. ;*** Sichert den bisherigen Interrupt-Vektor des Interrupt 09h, setzt
  552. ;*** den Vektor auf die eigene Interrupt-Routine und installiert es
  553. ;*** resident im Speicher, oder, falls VGASAVE bereits installiert
  554. ;*** war, entfernt sie VGASAVE wieder aus dem Speicher.
  555.  
  556. iniproc proc near
  557.  
  558. ;*** Feststellen, ob VGASAVE bereits installiert ist
  559.  
  560.         mov  ah,35h          ;DOS-Fkt. 35h: Interrupt-Vektor auslesen
  561.         mov  al,09h          ;Interrupt-Nummer 09h: Tastatur-Interrupt
  562.         int  21h             ;Vektor nach ES:BX holen
  563.         cmp  word ptr es:[bx+2],"gV";auf Identifikation testen
  564.         jne  intini          ;Ungleichheit => Installation
  565.         cmp  word ptr es:[bx+4],"Sa"
  566.         jne  intini
  567.         cmp  word ptr es:[bx+6],"va"
  568.         jne  intini
  569.         cmp  word ptr es:[bx+8],"!e"
  570.         jne  intini
  571.  
  572. ;*** Deinstallation
  573.  
  574. ;*** ehemaligen Interrupt-Vektor wiederherstellen
  575.  
  576.         mov  ah,25h          ;DOS-Fkt. 25h: Interrupt-Vektor setzen
  577.         mov  al,09h          ;Interrupt-Nummer 09h: Tastatur-Interrupt
  578.         mov  dx,es:altints   ;Segment-Adresse holen
  579.         mov  ds,dx           ;und nach DS bringen
  580.         mov  dx,es:altinto   ;Offset-Adresse nach DX bringen
  581.         int  21h             ;Vektor setzen/wiederherstellen
  582.  
  583. ;*** zu VGASAVE gehörenden Environment-Block freigeben
  584.  
  585.         push es              ;Segment-Adresse von VGASAVE merken
  586.         mov  ah,49h          ;DOS-Fkt. 49h: RAM freigeben
  587.         mov  es,es:[2ch]     ;PSP Offset 2ch: Segment-Adresse Env.-B.
  588.         int  21h             ;Speicher freigeben
  589.         pop  es              ;ES wiederherstellen
  590.  
  591. ;*** von VGASAVE belegten Speicher freigeben
  592.  
  593.         mov  ah,49h          ;DOS-Fkt. 49h: RAM freigeben
  594.         int  21h             ;ausführen (ES = Segment-Adresse VGASAVE)
  595.  
  596. ;*** Meldung ausgeben
  597.  
  598.         push cs              ;Datensegment := Codesegment, da sich
  599.         pop  ds              ;Daten im Codesegment befinden
  600.  
  601.         mov  ah,09h          ;DOS-Fkt. 09h: Zeichenkette ausgeben
  602.         mov  dx,offset meldaus;Adresse des Meldungstextes
  603.         int  21h             ;Ausgabe
  604.  
  605. ;*** Programm beenden
  606.  
  607.         mov  ah,4ch          ;DOS-Fkt. 4ch: Programm m. Ende-Code beenden
  608.         mov  al,0            ;Ende-Code 0: Kein Fehler
  609.         int  21h             ;Programm beenden
  610.  
  611. ;*** Installations-Routine für VGASAVE
  612.  
  613. ;*** bisherigen Interrupt-Vektor merken
  614.  
  615. intini: mov  altints,es      ;Segment-Adresse sichern
  616.         mov  altinto,bx      ;Offset-Adresse sichern
  617.  
  618. ;*** Dateinamen aus Parameterzeile holen
  619.  
  620.         push ds              ;ES auf DS setzen,
  621.         pop  es              ;ES wird beim Kopieren benutzt
  622.         mov  di,offset datname;Zeiger in Zielbereich initialisieren
  623.  
  624.         mov  si,parzeil      ;Quellzeiger auf Parameterzeile setzen
  625.         mov  cl,ds:anzpar    ;Anzahl Zeichen in Parameterzeile holen
  626.         mov  ch,0            ;auf 16 Bit bringen
  627.         cmp  cx,0            ;Anzahl mit 0 vergleichen
  628.         je   copy0           ;Datei-Name wurde nicht angegeben => Default
  629.  
  630.         dec  cl              ;Anzahl wegen Leerzeichen um 1 vermindern
  631.         jmp  short copy      ;zum Kopieren
  632.  
  633. copy0:  mov  si,offset defname;Quellzeiger auf Default-Dateinamen setzen
  634.         mov  cx,defnam0 - defname;Anzahl Zeichen ermitteln
  635.  
  636. copy:   cld                  ;Directionflag löschen => vorwärts kopieren
  637.         rep  movsb           ;Quellbereich nach Zielbereich kopieren
  638.         mov  datname[di],0   ;Endekennzeichen (Nullbyte) setzen
  639.  
  640. ;*** eigenen Interrupt installieren
  641.  
  642.         mov  ah,25h          ;DOS-Fkt. 25h: Interrupt-Vektor setzen
  643.         mov  al,09h          ;Interrupt 09h: Tastatur-Interrupt
  644.         mov  dx,offset neuint;Offset der eigenen Interrupt-Routine
  645.         int  21h             ;Interrupt-Vektor auf DS:DX setzen
  646.  
  647. ;*** Meldung ausgeben
  648.  
  649.         mov  ah,09h          ;DOS-Fkt. 09h: Zeichenkette ausgeben
  650.         mov  dx,offset inst_m;Adresse des Textes
  651.         int  21h             ;Ausgabe
  652.  
  653. ;*** nicht benötigten Speicher freigeben und Programm resident beenden
  654.  
  655.         mov  ah,31h          ;DOS-Fkt. 31h: Programm resident beenden
  656.         mov  al,0            ;Ende-Code 0: kein Fehler
  657.         mov  dx,offset residen;Grenze, bis zu der's resident bleibt
  658.         add  dx,15           ;auf nächsten Paragraphen aufrunden
  659.         mov  cl,4            ;4 mal Rechts-Shiften => durch 16 teilen
  660.         shr  dx,cl           ;Segment-Adresse errechnen
  661.         int  21h             ;Programm resident beenden
  662.  
  663. iniproc endp                 ;Ende der Prozedur
  664.  
  665. code    ends                 ;Ende des Code-Segments
  666.         end start            ;Ende des Programmtextes
  667. 
  668.