Catturare il contenuto del video

In questo esempio presentiamo un programma TSR che consente di scrivere in un file specificato dall'utente il contenuto del video (in modo testo) quando vengano premuti contemporaneamente i due tasti di shift. Il testo del buffer video è aggiunto ai dati eventualmente già presenti nel file indicato; al termine di ogni riga (e prima di quella iniziale) è aggiunta una sequenza CR LF; in coda al buffer è inserito un carattere ASCII 12 (FormFeed o salto pagina), rendendo in tal modo il tutto particolarmente idoneo al successivo editing mediante programmi di videoscrittura. 
/********************

   Barninga_Z! - 1991

   SHFVWRIT.C - TSR che scrive su un file il buffer video CGA, EGA,
                VGA in modo testo formattato mediante l'aggiunta di
                CR+LF in testa e a fine riga, e di FF in coda. Il nome
                del file deve essere specificato sulla command line.

                I bit del byte attributo (eccetto il bit del blink)
                sono invertiti per segnalare lo svolgimento
                dell'operazione e sono nuovamente invertiti al termine
                della scrittura su file (effettuata in append). Se
                l'applicazione interrotta "ridisegna" una parte del
                video durante la scrittura, i bit dei bytes attibuto
                di tale porzione del video non vengono invertiti al
                termine dell'operazione. Il TSR si attiva premendo
                contemporaneamente i due SHIFT.

        Compilato sotto TURBO C++ 1.01

                    tcc -O -d -rd -k- -Tm2 shfvwrit.c

********************/
#pragma  inline
#pragma  option -k-

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

#define PRG                  "SHFVWRIT"
#define YEAR                 "1991"
#define _BLANK_              ((char)32)
#define _FF_                 ((char)12)
#define _LF_                 ((char)10)
#define _CR_                 ((char)13)
#define _NROW_               25
#define _NCOL_               80
#define _VBYTES_             _NROW_*_NCOL_
#define _NCR_                (_NROW_+1)
#define _NLF_                _NCR_
#define _NFF_                1
#define _BUFDIM_             (_VBYTES_+_NCR_+_NLF_+_NFF_)
#define _MONO_VIDADDR_       ((char far *)0xB0000000L)
#define _COLR_VIDADDR_       ((char far *)0xB8000000L)
#define _SHFMASK_            ((char)3)
#define _MASK_               ((char)127)
#define _TSR_TEST_           0x97 /* shfvwrit e' residente ? */
#define _TSR_YES_            0xABFE        /* risposta = si, e' residente */
#define _PSPENVOFF_          0x2C        /* off del seg ptr all'env.in psp */
#define _FNMLEN_             80     /* lungh. max path con NULL finale */

#define  int09h   ((void (interrupt *)())(*((long *)TSRdata)))     /*dd*/
#define  int28h   ((void (interrupt *)())(*(((long *)TSRdata)+1))) /*dd*/
#define  int2Fh   ((void (interrupt *)())(*(((long *)TSRdata)+2))) /*dd*/
#define  fnameptr ((char far *)(*(((long *)TSRdata)+3))) /*dd*/
#define  strptr   ((char far *)(*(((long *)TSRdata)+4)))   /*dd*/
#define  shfflag  (*(((int *)TSRdata)+10))        /*dw*/
#define  exeflag  (*(((int *)TSRdata)+11))        /*dw*/
#define  shfstat  (*((char far *)(*(((int *)TSRdata)+12))))       /*dw*/
#define  vidstr   (((char *)TSRdata)+26)   /*arr*/
#define  vidatr   (((char *)TSRdata)+26+_BUFDIM_)  /*arr*/
#define  fname    (((char far *)TSRdata)+26+_BUFDIM_+_VBYTES_)      /*arr*/

#define  int09h_asm    TSRdata
#define  int28h_asm    TSRdata+4
#define  int2Fh_asm    TSRdata+8
#define  fnameptr_asm  TSRdata+12
#define  strptr_asm    TSRdata+16

#define  clear_stack()   asm {pop di;pop si;pop ds;pop es;pop dx;pop cx;pop bx;pop ax;}

void TSRdata(void);     /* consente l'uso del nome prima della definiz. */

void filewrit(void)
{
    strptr = vidstr;
    asm {
        push ds;
        mov ah,0x5B;
        xor cx,cx;     /* attributo normale */
        lds dx,dword ptr fnameptr_asm;
        int 0x21;      /* tenta di aprire un nuovo file */
        pop ds;
        mov bx,ax;
        cmp ax,0x50;   /* se ax=50h il file esiste gia' */
        jne _OPENED;
        push ds;
        mov ax,0x3D01;
        lds dx,dword ptr fnameptr_asm;
        int 0x21;      /* il file esiste: puo' essere aperto */
        pop ds;
        mov bx,ax;
        mov ax,0x4202;
        xor cx,cx;
        xor dx,dx;
        int 0x21;      /* va a EOF per effettuare l'append */
    }
_OPENED:
    asm {
        mov cx,_BUFDIM_;
        mov ah,0x40;
        push ds;
        lds dx,dword ptr strptr_asm;
        int 0x21;      /* scrive il buffer nel file */
        pop ds;
        mov ah,0x3E;
        int 0x21;      /* chiude il file */
    }
}

void vidstrset(void)
{
    register j, i;
    char far *vidptr, far *vidstop;
    char *atrptr;

    atrptr = vidatr;
    asm {
        push bp;
        push sp;
        mov ah,0x0F;
        int 0x10;
        pop sp;
        pop bp;
    }
    switch(_AL) {
        case 2:
        case 3:
            vidptr = _COLR_VIDADDR_;
            break;
        case 7:
            vidptr = _MONO_VIDADDR_;
            break;
        default:
            return;
    }
    vidstop = (vidptr += (_BH*_VBYTES_));
    strptr = vidstr;
    *strptr++ = _CR_;
    *strptr++ = _LF_;
    for(i = 0;i < _NROW_;i++) {
        for(j = 0;j < _NCOL_;j++,vidptr++) {
            *strptr++ = (*vidptr) ? *vidptr : _BLANK_;
            *atrptr++ = (*(++vidptr) ^= _MASK_);
        }
        *strptr++ = _CR_;
        *strptr++ = _LF_;
    }
    *strptr++ = _FF_;
    filewrit();
    for(;vidptr > vidstop;vidptr--)
        *vidptr = (*(--vidptr) == (*(--atrptr) ^= _MASK_)) ? *vidptr :
        *atrptr;
}

void interrupt newint28h(void)
{
    if(shfflag & (!exeflag)) {
        exeflag = 1;
        vidstrset();
        shfflag = exeflag = 0;
    }
    clear_stack(); /* macro per uscire da funzione interrupt */
    asm jmp dword ptr int28h_asm;
}

void interrupt newint09h(void)
{
    if(((shfstat & _SHFMASK_) == _SHFMASK_) && (!(shfflag | exeflag)))
        shfflag = 1;
    clear_stack(); /* macro per uscire da funzione interrupt */
    asm jmp dword ptr int09h_asm;
}

void far newint2Fh(void)        /* non interrupt: non serve salvare regs */
{
    asm {
        cmp al,0;
        jne _CHAIN;
        cmp ah,_TSR_TEST_;
        je _ANSWER;
    }
_CHAIN:
    asm jmp dword ptr int2Fh_asm;
_ANSWER:
    asm {
        mov ax,_TSR_YES_;
        iret;
    }
}

void TSRdata(void)
{
    asm {
        dd 0;  /* ptr int 09h */
        dd 0;  /* ptr int 28h */
        dd 0;  /* ptr int 2Fh */
        dd 0;  /* ptr al nome del file */
        dd 0;  /* ptr all'array contenente copia del video */
        dw 0;  /* flag richiesta popup */
        dw 0;  /* flag popup attivo */
        dw 0x417;      /* ptr shift status byte */
        db _BUFDIM_ dup (0);   /* buffer dati dal video al file */
        db _VBYTES_ dup (0);   /* buffer bytes-attributo dal video */
        db _FNMLEN_ dup (0);   /* pathname del file di output */
    }
}

int tsrtest(void)
{
    asm {
        mov ah,_TSR_TEST_;
        xor al,al;
        int 0x2F;
        cmp ax,_TSR_YES_;
        je _RESIDENT;
        xor ax,ax;
    }
_RESIDENT:
    return(_AX);
}

void release_env(void)
{
    asm {
        mov ax,_psp;   /* tramite AX... */
        mov es,ax;     /* ...carica in ES l'ind. di segmento del PSP */
        mov bx,_PSPENVOFF_;    /* ES:BX punta all'indirizzo dell'Env. */
        mov ax,es:[bx];        /* salva in AX l'ind di segmento dell'Env. */
        mov es,ax;     /* per poi caricarlo in ES */
        mov ah,0x49;   /* libera il blocco di memoria tramite il DOS */
        int 0x21;      /* int 21h servizio 49h */
    }
}

void install(void)
{
    asm cli;
    int09h = getvect(0x09);
    int28h = getvect(0x28);
    int2Fh = getvect(0x2F);
    setvect(0x09,newint09h);
    setvect(0x28,newint28h);
    setvect(0x2F,(void (interrupt *)())newint2Fh);
    asm sti;
    release_env();
    _DX = FP_SEG(fnameptr)-_psp+1+((FP_OFF(fnameptr)+_FNMLEN_) >> 4);
    asm {
        mov ax,0x3100;
        int 0x21;
    }
}

int filetest(char *argv1)
{
    register i;

    fnameptr = fname;
    for(i = 0; argv1[i]; i++)
        fname[i] = argv1[i];
    asm {
        push ds;
        lds si,dword ptr fnameptr_asm;
        les di,dword ptr fnameptr_asm;
        mov ah,0x60;   /* ricava il pathname completo */
        int 0x21;      /* ATTENZIONE: e' un servizio DOS non documentato!! */
        lds dx,dword ptr fnameptr_asm;
        xor cx,cx;     /* attributo normale */
        mov ah,0x5B;
        int 0x21;      /* tenta di aprire un nuovo file */
        jnc _OPENED_NEW;       /* file aperto: il nome e' ok */
        cmp ax,0x50;   /* se AX=50h il file esiste: nome ok */
        je _FNAME_OK;
        jmp _EXITFUNC:
    }
_OPENED_NEW:
    asm {
        mov bx,ax;
        mov ah,0x3E;
        int 0x21;      /* chiude il file */
5        mov ah,0x41;
        int 0x21;      /* e lo cancella */
    }
_FNAME_OK:
    asm xor ax,ax;
_EXITFUNC:
    asm pop ds;
    return(_AX);
}

void main(int argc,char **argv)
{
    (void)puts(PRG" - Barninga_Z! - Torino - "YEAR".");
    if(argc != 2)
        (void)puts(PRG": sintassi: shfvwrit [d:][path]file[.ext]");
    else
        if(tsrtest())
            (void)puts(PRG": già residente in RAM.");
        else
            if(filetest(argv[1]))
                (void)puts(
                      PRG": impossibile aprire il file specificato.");
            else {
                (void)puts(
                 PRG": per attivare premere LeftShift e RightShift.");
                install();
            }
}
La struttura del programma non è particolarmente complessa. La main() controlla che sia stato fornito, via command line, un nome di file per l'output: in caso negativo l'elaborazione viene interrotta. La funzione tsrtest() verifica l'eventuale presenza in RAM del TSR, utilizzando l' int2Fh. Se il programma non è già residente, filetest() controlla la validità del nome di file specificato dall'utente: il   servizio60h dell'int21h è utilizzato per ricavarne il pathname completo, onde evitare che variazioni dei default relativi a drive e directory di lavoro determinino la scrittura dei dati in luoghi imprevisti. La main() invoca poi install(), che completa la fase di installazione: essa, in primo luogo, salva i vettori degli interrupt utilizzati dal programma e sostituisce ad essi quelli dei nuovi gestori; in seguito invoca release_env(), che libera la RAM occupata dall'environment fornito dal DOS al TSR, dal momento che questo non ne fa uso. Infine install() calcola il numero di paragrafi che devono rimanere residenti e installa SHFVWRIT mediante l'int 21h, servizio 31h, "cuore" della funzione di libreria keep(), qui invocato direttamente per rendere il codice più compatto. 

Tutti i dati globali necessari al programma sono gestiti nello spazio ad essi riservato, all'interno del code segment, dalla funzione jolly TSRdata(), che chiude il gruppo delle routine residenti. Le macro atte a facilitare i riferimenti a detti dati sono definite, nel sorgente, al termine delle direttive #define relative alle costanti manifeste. Disorientati? 

La funzione newint2Fh() è il nuovo gestore dell' int2Fh: essa è dichiarata far in quanto la semplicità della sua struttura rende inutile il salvataggio automatico dei registri. Il concatenamento al gestore originale (nel caso in cui SHFVWRIT non sia ancora installato) è effettuato mediante una istruzione JMP; il ritorno al processo chiamante (SHFVWRIT installato) mediante una IRET. In entrambi i casi è indispensabile ripristinare lo stack quale esso appare al momento dell'ingresso nella funzione: a seconda della versione del compilatore può essere necessario, a tal fine, effettuare una POP del registro BP

La funzione newint09h() è il nuovo gestore dell' int09h: esso si occupa semplicemente di analizzare il byte di stato degli shift per intercettare lo hotkey. Se l'utente preme i due shift e, al tempo stesso, il TSR non è attivo (exeflag = 0) viene posto a 1 il flag indicante la richiesta di popup, cioè di attivazione. Anche in questo caso il concatenamento al gestore originale è effettuato con una JMP; lo stack è ripristinato dalla macro clear_stack(), precedentemente definita. La newint09h() è dichiarata interrupt per sicurezza. Essa, infatti, gestisce un interrupt hardware che viene invocato da un evento asincrono; inoltre contiene quasi esclusivamente codice C, con la conseguenza che non è possibile sapere a priori quali registri vengano da essa modificati. In questo caso appare prudente (e comodo) lasciare al compilatore il compito di salvare in modo opportuno i registri della CPU. 

La newint28h() gestisce l' int28h: essa è pertanto invocata dal DOS quando questo è in attesa di un input dalla tastiera. Se è stato richiesto il popup e il TSR non è attivo, viene posto a 1 il flag che ne indica l'attivazione ed è invocata vidstrset(). Al rientro sono azzerati i flag e viene concatenato il gestore originale. Anche newint28h() è scritta quasi interamente in linguaggio C, pertanto è dichiarata interrupt

La funzione vidstrset() pilota le operazioni di lettura del buffer video e di preparazione alla scrittura nel file. Essa verifica la modalità video attuale mediante il servizio 0Fh dell' int10h: se è grafica o testo a 40 colonne il controllo è restituito a newint28h() senza intraprendere alcuna azione, altrimenti viene opportunamente determinato l'indirizzo del buffer video. 

Mediante indirezioni e incrementi di puntatori, il testo contenuto nel buffer è copiato nello spazio ad esso riservato nella TSRdata(), inserendo al tempo stesso i caratteri necessari per dare al testo il formato voluto; inoltre gli ASCII 0 sono trasformati in ASCII 32 (spazio). I byte attributo del buffer, e dunque i colori del testo visualizzato, dopo essere stati anch'essi salvati in un array collocato nella TSRdata(), sono modificati tramite un'operazione di XOR con un byte che funge da maschera: lo scopo è segnalare visivamente che SHFVWRIT è in azione. Al termine di queste operazioni è invocata filewrit() e al rientro da questa una nuova operazione di XOR, identica alla precedente, ripristina i colori originali del testo nelle sole aree di video non modificate da altre applicazioni durante l'attività di filewrit()

Quest'ultima si occupa delle operazioni di output nel file specificato dall'utente: dal momento che il testo deve essere aggiunto al contenuto del file, è necessario che esso sia creato se non esiste, ma non distrutto se è già presente. Il servizio 5Bh dell'int 21h tenta l'apertura di un nuovo file: se questo esiste l'operazione fallisce (il servizio 3Ch ne avrebbe troncato a 0 la lunghezza) e il file può essere aperto con il servizio 3Dh. Dopo l'operazione di scrittura il file viene chiuso: in tal modo il DOS aggiorna correttamente FAT e directory. La filewrit() è scritta quasi per intero in assmbly inline non solo per ragioni di velocità e compattezza del codice, ma anche (e soprattutto) per evitare l'uso di funzioni di libreria[1] e, di conseguenza, i problemi legati al loro utilizzo nella parte residente dei TSR

Concludiamo il commento al codice di SHFVWRIT evidenziandone le carenze, dovute alla necessità di presentare un esempio di facile comprensione. Le routine residenti non segnalano all'utente il verificarsi di eventuali errori (modalità video non prevista, disco pieno, etc.) e mancano gestori per gli int 23h e 1Bh (CTRL­C, CTRL­BREAK) e per l' int24h (errore critico), la gestione dei quali è quindi lasciata all'applicazione interrotta. Il limite più evidente è però l'utilizzo dell' int28h come punto di attivazione: detto interrupt è infatti generato dal DOS nei loop interni alle funzioni 01h­0Ch dell'int 21h, cioè, in prima approssimazione, quando esso è in attesa di un input da tastiera. Se l'applicazione interrotta non si avvale del DOS per gestire la tastiera ma, piuttosto, del BIOS ( int16h), newint09() può ancora registrare nell'apposito flag la richiesta di popup, ma questo non avviene sino al termine dell'applicazione stessa. 

Presentiamo di seguito il listato del programma VIDEOCAP, che rappresenta una evoluzione di SHFVWRIT. In particolare VIDEOCAP utilizza ancora quale punto di attivazione l' int28h, ma incorpora un gestore dell' int16h (newint16h()), il quale simula con un loop sul servizio 01h/11h le richieste di servizio 00h/10h; in tale loop viene invocato un  int28h (se non sono in corso servizi DOS) consentendo così l'attivazione anche sotto programmi che gestiscono la tastiera via  int16h. La routine di copia del buffer video individua il modo video attuale (ammessi testo 80 colonne colore o mono), il numero di righe video attuali e la pagina corrente, risultando così più flessibile e completa di quella incorporata da SHFVWRIT. L'offset della pagina video corrente nel buffer video è determinato moltiplicando 160 (80 2) per il numero di righe correnti e sommando un correttivo (individuato empiricamente) descritto in una tabella hard-coded, alla quale è riservato spazio dalla funzione fittizia rowFactors()

Si noti che le funzioni fittizie sono dichiarate DUMMY, così come il tipo di parametro richiesto. L'identificatore di tipo DUMMY è, in realtà, definito e reso equivalente a void dalla riga 
typedef void DUMMY;
Si tratta dunque di uno stratagemma volto ad aumentare la leggibilità del programma (lo specificatore typedef definisce sinonimi per i tipi di dato). 

VIDEOCAP, a differenza di SHFVWRIT, impiega una funzione fittizia per ogni variabile globale residente: in tal modo è più semplice referenziarle mediante operazioni di cast e si evitano alcune #define

Anche la modalità in cui il programma segnala di avere eseguito il proprio compito è radicalmente diverso: invece di modificare il colore dei caratteri a video durante l'operazione, VIDEOCAP emette un breve beep a 2000 Hz non appena chiuso il file. La funzione beep2000Hz() esemplifica come pilotare l'altoparlante del PC in una routine residente. 
/******************************************************************************

    VIDEOCAP.C - Barninga_Z! - 09/11/92

        Utility TSR per salvataggio del video su file. Invocare con un nome
        di file per installare in memoria. Il dump su file si ottiene
        premendo contemporaneamente i due tasti di shift. Per disinstallare
        invocare con un asterisco come parametro sulla command line.

        Compilato sotto BORLAND C++ 3.1:

            bcc -Tm2 -O -d -rd -k- videocap.c

******************************************************************************/
#pragma inline
#pragma warn -pia

#include <stdio.h>      // la #include <io.h> deve essere DOPO tutte le
#include <dos.h>        // funzioni DUMMY contenenti asm ... dup(...)
#include <string.h>     // perche' in io.h e' definita int dup(int)

#define PRG            "VIDEOCAP"
#define VER            "1.0"
#define YEAR           "1992"
#define CRLF           0A0Dh      // backwords
#define UNINSTALL      '*'   // opzione disinstallazione
#define BLANK          32
#define FORMFEED       12
#define _NCOL_         80
#define _MAXROW_       50
#define _BUFDIM_       ((_NCOL_*_MAXROW_)+(2*_MAXROW_)+3)  // b*h+b*CRLF+CRLFFF
#define _MONO_V_SEG_   0B000h
#define _COLOR_V_SEG_  0B800h
#define _SHFMASK_      3
#define _TSR_TEST_     0xA1                          // videocap e' residente ?
#define _TSR_YES_      0xFF16        // risposta = si, e' residente
#define _FNAMELEN_     81   // lungh. max pathname compreso NULL finale
#define  BADCHARS      ";,:|><"       // caratteri illeciti nei nomi di file

typedef     void       DUMMY;

int pathname(char *path,char *src,char *badchrs);
char far *getInDOSaddr(void);

DUMMY resPSP(DUMMY)
{
    asm dw 0;
}

DUMMY inDosFlagPtr(DUMMY)
{
    asm dd 0;
}

DUMMY old09h(DUMMY)
{
    asm dd 0;
}

DUMMY old16h(DUMMY)
{
    asm dd 0;
}

DUMMY old28h(DUMMY)
{
    asm dd 0;
}

DUMMY old2Fh(DUMMY)
{
    asm dd 0;
}

DUMMY opReq(DUMMY)
{
    asm db 0;
}

DUMMY inOp(DUMMY)
{
    asm db 0;
}

DUMMY rowFactors(DUMMY) // fattori di offset per pagine video su VGA
{
    asm db  48, 12; // fattore offset, numero righe (AL,AH) da
    asm db  48, 25; // utilizzare per calcolare l'offset della
    asm db 112, 29;        // pagina attiva nel buffer video
    asm db  16, 43;
    asm db  96, 50;
    asm db   0,  0;   // tappo (segnala la fine della tabella)
}

DUMMY fileName(DUMMY)
{
    asm db _FNAMELEN_ dup(0);
}

DUMMY bufVid(DUMMY)
{
    asm db _BUFDIM_ dup(BLANK);
}

#include <io.h>   // definisce dup(int); va incluso DOPO tutte le asm XX dup(Y)

void beep2000Hz(void)
{
    asm in al,61h; // prepara PC speaker
    asm or al,3;
    asm out 61h,al;
    asm mov al,0B6h;
    asm out 43h,al;
    asm mov al,054h;
    asm out 42h,al;
    asm mov al,2;
    asm out 42h,al;                                          // suona a 2000 Hz 
    asm mov cx,0FFFFh;
  DELAY:
    asm jmp $ + 2;
    asm loop DELAY;
    asm in al,61h;
    asm and al,0FCh;
    asm out 61h,al;        // esclude PC speaker
}

void writebufVid(int rows)
{
    asm push ds;
    asm xor cx,cx; // attributo normale
    asm mov ax,seg fileName;
    asm mov ds,ax;
    asm mov dx,offset fileName;
    asm mov ah,0x5B;
    asm int 0x21;  // tenta di aprire un nuovo file
    asm pop ds;
    asm mov bx,ax;
    asm cmp ax,0x50;       // se ax=50h il file esiste gia'
    asm jne OPENED;
    asm push ds;
    asm mov ax,seg fileName;
    asm mov ds,ax;
    asm mov dx,offset fileName;
    asm mov ax,0x3D01;
    asm int 0x21;  // il file esiste: puo' essere aperto 
    asm pop ds;
    asm mov bx,ax;
    asm mov ax,0x4202;
    asm xor cx,cx;
    asm xor dx,dx;
    asm int 0x21;  // va a EOF per effettuare l'append
  OPENED:
    asm push ds;
    asm mov ax,_NCOL_;
    asm mov cx,rows;
    asm mul cl;    // AX = AL*CL (colonne * righe)
    asm push ax;
    asm mov ax,2;
    asm mul cl;    // AX = AL*CL (righe*2; spazio CRLF)
    asm pop cx;
    asm add cx,ax; // CX = spazio totale righe * colonne
    asm add cx,3;  // CX = spazio totale con CRLF 1^ riga + FF finale
    asm mov ax,seg bufVid;
    asm mov ds,ax;
    asm mov dx,offset bufVid;
    asm mov ah,0x40;
    asm int 0x21;  // scrive il buffer nel file
    asm pop ds;
    asm mov ah,0x3E;
    asm int 0x21;  // chiude il file
    asm call _beep2000Hz;
}

int getOffsetByRow(void)
{
    asm push ds;
    asm mov ax,seg rowFactors;
    asm mov ds,ax;
    asm mov si,offset rowFactors;
  NEXT_FACTOR:
    asm lodsw;
    asm cmp ax,0;
    asm je END_OF_TABLE;   // non trovato n.righe in tabella -> offset = 0
    asm cmp ah,cl;
    asm jne NEXT_FACTOR;
    asm xor ah,ah;
    asm mov bx,2;
    asm mul bx;    // raddoppia offset per contare attributi video
  END_OF_TABLE:
    asm pop ds;
    return(_AX);   // AX = offset correttivo
}

int setbufVid(void)
{
    asm push ds;
    asm xor dl,dl; // valore restituito: righe
    asm mov ax,_COLOR_V_SEG_;      // video se modo video ok;
    asm mov ds,ax; // altrimenti 0
    asm mov ah,0Fh;
    asm int 10h;
    asm push bx;   // BH = pagina video attiva
    asm cmp al,2;
    asm je GETROWS;
    asm cmp al,3;
    asm je GETROWS;
    asm cmp al,7;
    asm je MONO;
    asm pop bx;
    asm jmp EXIT_FUNC;
  MONO:
    asm mov ax,_MONO_V_SEG_;
    asm mov ds,ax;
  GETROWS:
    asm mov ax,1130h;
    asm xor bh,bh;
    asm push bp;
    asm int 10h;
    asm pop bp;
    asm inc dl;    // numero righe display
    asm xor ch,ch;
    asm mov cl,dl;
    asm pop bx;
    asm mov bl,bh;
    asm xor bh,bh;
    asm mov ax,_NCOL_ * 2;
    asm push dx;
    asm mul cx;
    asm mul bx;    // AX = offset in buf. video della pagina attiva
    asm push ax;
    asm push bx;   // salva AX e BX
    asm call _getOffsetByRow;      // restituisce AX = offset correttivo
    asm pop bx;    // BX = num. pag.
    asm mul bx;    // offset * num. pagina; AX = totale correttivo
    asm pop bx;    // BX = offset base
    asm add ax,bx; // AX = offset totale in buf. video
    asm pop dx;
    asm mov si,ax; // DS:SI -> video
    asm mov ax,seg bufVid;
    asm mov es,ax;
    asm mov di,offset bufVid;      // ES:DI -> buffer
  NEXTROW:
    asm mov word ptr es:[di],CRLF;
    asm add di,2;
    asm push cx;
    asm mov cx,_NCOL_;
  ROWCOPY:
    asm lodsb;
    asm cmp al,0;  // NULL -> BLANK
    asm jne NOT_NULL;
    asm mov al,BLANK;
  NOT_NULL:
    asm stosb;
    asm inc si;    // salta attributo
    asm loop ROWCOPY;
    asm pop cx;
    asm loop NEXTROW;
    asm mov word ptr es:[di],CRLF;
    asm add di,2;
    asm mov byte ptr es:[di],FORMFEED;
  EXIT_FUNC:
    asm pop ds;
    return(_DL);
}

void far new09h(void)
{
    asm push ax;
    asm push bx;
    asm push ds;
    asm cmp byte ptr opReq,0;
    asm jne EXIT_INT;
    asm cmp byte ptr inOp,0;
    asm jne EXIT_INT;
    asm xor ax,ax;
    asm mov ds,ax;
    asm mov bx,0417h;      // indir. shift status byte
    asm mov al,byte ptr [bx];
    asm and al,_SHFMASK_;
    asm cmp al,_SHFMASK_;
    asm jne EXIT_INT;
    asm mov byte ptr opReq,1;
  EXIT_INT:
    asm pop ds;
    asm pop bx;
    asm pop ax;
    asm jmp dword ptr old09h;
}

void far new16h(void)
{
    asm sti;
    asm cmp ah,0;
    asm je SERV_0;
    asm cmp ah,10h;
    asm je SERV_0;
    asm cmp ah,1;
    asm je SERV_1;
    asm cmp ah,11h;
    asm je SERV_1:
    asm jmp EXIT_INT;
  SERV_1:
    asm int 28h;
    asm jmp EXIT_INT;
  SERV_0:
    asm push dx;
    asm mov dx,ax;
    asm inc dh;
  LOOP_0:
    asm mov ax,dx;
    asm cli;
    asm pushf;
    asm call dword ptr old16h;
    asm jnz KEY_READY;
    asm push ds;
    asm push bx;
    asm lds bx,dword ptr inDosFlagPtr;
    asm cmp byte ptr [bx],0;
    asm pop bx;
    asm pop ds;
    asm jne LOOP_0;
    asm sti;
    asm int 28h;
    asm jmp LOOP_0;
  KEY_READY:
    asm mov ax,dx;
    asm dec ah;
    asm pop dx;
  EXIT_INT:
    asm jmp dword ptr old16h;
}

void interrupt new28h(void)
{
    asm cmp byte ptr opReq,0;
    asm je CALL_OLDINT;
    asm cmp byte ptr inOp,0;
    asm jne CALL_OLDINT;
    asm mov byte ptr inOp,1;
    asm call _setbufVid;
    asm cmp ax,0;
    asm je DONE;
    asm push ax;
    asm call _writebufVid;
    asm pop cx;
  DONE:  
    asm mov byte ptr inOp,0;
    asm mov byte ptr opReq,0;
  CALL_OLDINT:
    asm pushf;
    asm call dword ptr old28h;
}

void s2Funinstall(void)
{
    asm push ds;
    asm xor ax,ax;
    asm mov es,ax;
    asm mov ax,seg old09h;
    asm mov ds,ax;
    asm mov si,offset old09h;
    asm mov di,0x09 * 4;
    asm mov cx,2;
    asm rep movsw;
    asm mov ax,seg old16h;
    asm mov ds,ax;
    asm mov si,offset old16h;
    asm mov di,0x16 * 4;
    asm mov cx,2;
    asm rep movsw;
    asm mov ax,seg old28h;
    asm mov ds,ax;
    asm mov si,offset old28h;
    asm mov di,0x28 * 4;
    asm mov cx,2;
    asm rep movsw;
    asm mov ax,seg old2Fh;
    asm mov ds,ax;
    asm mov si,offset old2Fh;
    asm mov di,0x2F * 4;
    asm mov cx,2;
    asm rep movsw;
    asm pop ds;
}

void far new2Fh(void)
{
    asm cmp ah,_TSR_TEST_;
    asm je NEXT0;
    asm jmp CHAIN_INT;
  NEXT0:
    asm cmp al,0;
    asm jne NEXT1;
    asm mov ax,_TSR_YES_;
    asm jmp EXIT_INT;
  NEXT1:
    asm cmp al,UNINSTALL;
    asm jne NEXT2;
    asm call _s2Funinstall;
    asm mov ax,word ptr resPSP;
    asm jmp EXIT_INT;
  NEXT2:
  CHAIN_INT:
    asm jmp dword ptr old2Fh;
  EXIT_INT:
    asm iret;
}

void releaseEnv(void)
{
    extern unsigned _envseg;

    if(freemem(_envseg))
        puts(PRG": impossibile liberare l'environment.");
}

void install(void)
{
    extern unsigned _psp;

    (char far *)*(long far *)inDosFlagPtr = getInDOSaddr();
    *(unsigned far *)resPSP = _psp;
    releaseEnv();
    asm cli;
    (void(interrupt *)(void))*((long far *)old09h) = getvect(0x09);
    (void(interrupt *)(void))*((long far *)old16h) = getvect(0x16);
    (void(interrupt *)(void))*((long far *)old28h) = getvect(0x28);
    (void(interrupt *)(void))*((long far *)old2Fh) = getvect(0x2F);
    setvect(0x09,(void (interrupt *)(void))new09h);
    setvect(0x16,(void (interrupt *)(void))new16h);
    setvect(0x28,new28h);
    setvect(0x2F,(void (interrupt *)(void))new2Fh);
    asm sti;
    puts(PRG": per attivare premere LShift e RShift. "PRG" *  disinstalla.");
    keep(0,FP_SEG(releaseEnv) + (FP_OFF(releaseEnv) / 16) - _psp + 1);
}

int tsrtest(void)
{
    _AL = 0;
    _AH = _TSR_TEST_;
    geninterrupt(0x2F);
    return(_AX == _TSR_YES_);
}

int filetest(char *fname)
{
    FILE *testFile;
    int newFile;
    char path[_FNAMELEN_];

    if(pathname(path,fname,BADCHARS))
        return(1);
    newFile = access(path,0);
    if(!(testFile = fopen(path,"a")))
        return(1);
    fclose(testFile);
    _fstrcpy((char far *)fileName,(char far *)path);
    if(newFile)
        return(unlink(path));
    return(0);
}

void uninstall(void)
{
    _AH = _TSR_TEST_;
    _AL = UNINSTALL;
    geninterrupt(0x2F);
    if(freemem(_AX))
        puts(PRG": impossibile liberare la memoria allocata.");
    else
        puts(PRG": disinstallato. Vettori ripristinati e RAM liberata.");
}

void main(int argc,char **argv)
{
    puts(PRG" "VER" - Barninga_Z! - Torino - "YEAR".");
    if(argc != 2)
        puts(PRG": sintassi: "PRG" [d:][path]file[.ext] | *");
    else
        if(tsrtest())
            if(*argv[1] == UNINSTALL)
                uninstall();
            else
                puts(PRG": già residente in RAM.");
        else
            if(filetest(argv[1]))
                puts(PRG": impossibile aprire il file specificato.");
            else 
                install();
}
VIDEOCAP chiama due funzioni delle quali, per brvità, il codice comprende esclusivamente i prototipi: si tratta di pathname(), utilizzata per ricavare il path completo del file fornito come parametro sulla riga di comando, e di getInDOSaddr(), che restituisce l'indirizzo dell'InDOS Flag

Infine, si noti che lo header file IO.H è incluso dopo la definizione di tutte le funzioni fittizie, per evitare che il compilatore interpreti come chiamate alla funzione dup(), in esso dichiarata, le direttive assembly DUP utilizzate per riservare spazio alle variabili globali residenti .

OK, andiamo avanti a leggere il libro... 

Non ci ho capito niente! Ricominciamo...