/*****************************************************************************
* Test program for the TSR.ASM module (only in tiny model)		     *
*					Written by Gershon Elber, Sep. 1990  *
*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <dos.h>
#include <io.h>
#include "tsr.h"

#define VERSION "1.1"

#define SAVE_FILENAME_RAW "GrScr%03d.raw"
#define SAVE_FILENAME_BAT "GrScr%03d.bat"
#define SAVE_FILENAME_CMAP "GrScr%03d.cmp"
#define SAVE_FILENAME_GIF "GrScr%03d.gif"

#define MAX_LINE_LEN 1024

typedef enum {
    NO_DEVICE,
    HERCULES,
    CGA_LOW,
    CGA_HI,
    EGA_LOW,
    EGA_MED,
    VGA_LOW,
    VGA_MED,
    VGA_HI,
    SVGA,
    UVGA
} DeviceType;

static int NumOfColors = 16, ColorMask = 15,
    DeviceXMax, DeviceYMax, DeviceBase;
static DeviceType Device = NO_DEVICE;
static char *Usage =
    "Usage: GrabScrn [-d] Device\t"
    "(C) Copyright Gershon Elber, Ver " VERSION ", Sep 1990.\n"
    "where: d is one digit {1..8} and Device can be one of:\n"
    "       HERCULES CGAL CGAH EGAL EGAM VGAL VGAM VGAH SVGA UVGA.\n";
static unsigned char ScanLine[MAX_LINE_LEN];

static void GetScanLine(unsigned char *ScanLine, int Y);
static void TsrFunc(void);

/******************************************************************************
* main - set and install the screen grabber.				      *
******************************************************************************/
void main(int argc, char **argv)
{
    int far *n;
    DeviceType far *d;

    NumOfColors = 16;

    if (argc > 2) {
    	if (argv[1][0] == '-' &&
	    argv[1][1] >= '1' && argv[1][1] <= '8') {
	    /* -digit detected: force the number of colors to use. */
            NumOfColors = 2 << (argv[1][1] - '1');
    	    argc--;
            argv++;
	}
    	else {
	    fprintf(stderr, "Syntax error.\n");
	    fprintf(stderr, Usage);
	    exit(2);
    	}
    }

    if (argc != 2) {
	fprintf(stderr, Usage);
	exit(1);
    }
    if (strlen(argv[1]) < 4)
	argv[1][4] = 0;			 /* Trim the Device to 4 characters. */

    if (strcmp(argv[1], "HERC") == 0) {
	Device = HERCULES;
        NumOfColors = 2;
    }
    else if (strcmp(argv[1], "CGAL") == 0) {
	Device = CGA_LOW;
        NumOfColors = 4;
    }
    else if (strcmp(argv[1], "CGAH") == 0) {
	Device = CGA_HI;
        NumOfColors = 2;
    }
    else if (strcmp(argv[1], "EGAL") == 0) {
	Device = EGA_LOW;
    }
    else if (strcmp(argv[1], "EGAM") == 0) {
	Device = EGA_MED;
    }
    else if (strcmp(argv[1], "VGAL") == 0) {
	Device = VGA_LOW;
    }
    else if (strcmp(argv[1], "VGAM") == 0) {
	Device = VGA_MED;
    }
    else if (strcmp(argv[1], "VGAH") == 0) {
	Device = VGA_HI;
    }
    else if (strcmp(argv[1], "SVGA") == 0) {
	Device = SVGA;
    }
    else if (strcmp(argv[1], "UVGA") == 0) {
	Device = UVGA;
    }
    else {
	fprintf(stderr, "Undefined device type \"%s\".\n", argv[1]);
	fprintf(stderr, Usage);
	exit(2);
    }

    switch (HookKbdInterrupt(TsrFunc, 0x4408 /* ALT F10 */)) {
	case 0:
	    fprintf(stderr, "GrabScrn is installed, ALT-F10 To activate.\n");
	    keep(0, 0x400);
	    break;
	case 1:
	    fprintf(stderr,
		    "GrabScrn is already installed, device set to \"%s\", %d Colors.\n",
		    argv[1], NumOfColors);
	    d = MK_FP(InstalledTSRSegment, &Device);
	    *d = Device;
	    n = MK_FP(InstalledTSRSegment, &NumOfColors);
            *n = NumOfColors;
	    break;
	case 2:
	    fprintf(stderr, "GrabScrn failed to install.\n");
	    break;
    }
}

/******************************************************************************
* Set device parameters.						      *
******************************************************************************/
static void SetDeviceParams(void)
{
    switch (Device) {
	case HERCULES:
    	    DeviceYMax = 350;
    	    DeviceXMax = 720;
	    DeviceBase = 0xb000;
	    break;
	case CGA_LOW:
    	    DeviceYMax = 200;
	    DeviceXMax = 320;
	    DeviceBase = 0xb800;
	    break;
	case CGA_HI:
    	    DeviceYMax = 200;
	    DeviceXMax = 640;
	    DeviceBase = 0xb800;
	    break;
	case EGA_LOW:
    	    DeviceYMax = 200;
	    DeviceXMax = 640;
	    DeviceBase = 0xa000;
	    break;
	case EGA_MED:
    	    DeviceYMax = 350;
    	    DeviceXMax = 640;
    	    DeviceBase = 0xa000;
	    break;
	case VGA_LOW:
    	    DeviceYMax = 200;
    	    DeviceXMax = 640;
    	    DeviceBase = 0xa000;
	    break;
	case VGA_MED:
    	    DeviceYMax = 350;
    	    DeviceXMax = 640;
    	    DeviceBase = 0xa000;
	    break;
	case VGA_HI:
    	    DeviceYMax = 480;
    	    DeviceXMax = 640;
    	    DeviceBase = 0xa000;
	    break;
	case SVGA:
    	    DeviceYMax = 600;
    	    DeviceXMax = 800;
    	    DeviceBase = 0xa000;
	    break;
	case UVGA:
    	    DeviceYMax = 768;
    	    DeviceXMax = 1024;
    	    DeviceBase = 0xa000;
	    break;
    }
    ColorMask = NumOfColors - 1;
}

/******************************************************************************
* Update the given scan line buffer with the pixel levels of the Y line.      *
* This routine is device specific, so make sure you know was you are doing    *
******************************************************************************/
static void GetScanLine(unsigned char *ScanLine, int Y)
{
    unsigned char DeviceByte;
    int i, j, k;
    unsigned int DeviceOffset, Bit;
    union REGS InRegs, OutRegs;

    switch (Device) {
	case HERCULES:
	    DeviceOffset = 0x2000 * (Y % 4) + (Y / 4) * (DeviceXMax / 8);
	    /* In one scan lines we have DeviceXMax / 8 bytes: */
	    for (i = 0, k = 0; i < DeviceXMax / 8; i++) {
		DeviceByte = (unsigned char) peekb(DeviceBase, DeviceOffset++);
		for (j = 0, Bit = 0x80; j < 8; j++) {
		    ScanLine[k++] = (DeviceByte & Bit ? 1 : 0);
		    Bit >>= 1;
		}
	    }
	    break;
	case CGA_LOW:
	case CGA_HI:
	case EGA_LOW:
	case EGA_MED:
	case VGA_LOW:
	case VGA_MED:
	case VGA_HI:
	case SVGA:
	case UVGA:
	    InRegs.x.dx = Y;
	    InRegs.h.bh = 0;
    	    InRegs.h.ah = 0x0d;				   /* BIOS Read dot. */
	    for (i = 0; i < DeviceXMax; i++) {
	    	InRegs.x.cx = i;
		int86(0x10, &InRegs, &OutRegs);
	    	ScanLine[i] = OutRegs.h.al & ColorMask;
	    }

	    /* Mark this line as done by putting a xored dot on the left. */
	    InRegs.h.bh = 0;
	    InRegs.h.ah = 0x0c;		       /* BIOS Write dot (xor mode). */
	    InRegs.h.al = 0x81;			        /* Xor with color 1. */
	    InRegs.x.cx = 0;
	    InRegs.x.dx = Y;
	    int86(0x10, &InRegs, &OutRegs);
	    InRegs.h.bh = 0;
	    InRegs.h.ah = 0x0c;		       /* BIOS Write dot (xor mode). */
	    InRegs.h.al = 0x81;			        /* Xor with color 1. */
	    InRegs.x.cx = 1;
	    InRegs.x.dx = Y;
	    int86(0x10, &InRegs, &OutRegs);

	    if (Y == DeviceYMax - 1) {/* Last row - clear all marks we made. */
		for (i = 0; i < DeviceYMax; i++) {
		    InRegs.h.bh = 0;
		    InRegs.h.ah = 0x0c;	       /* BIOS Write dot (xor mode). */
		    InRegs.h.al = 0x81;		   /* Xor back with color 1. */
		    InRegs.x.cx = 0;
		    InRegs.x.dx = i;
		    int86(0x10, &InRegs, &OutRegs);
		    InRegs.h.bh = 0;
		    InRegs.h.ah = 0x0c;	       /* BIOS Write dot (xor mode). */
		    InRegs.h.al = 0x81;		   /* Xor back with color 1. */
		    InRegs.x.cx = 1;
		    InRegs.x.dx = i;
		    int86(0x10, &InRegs, &OutRegs);
                }
	    }
	    break;
	default:
	    break;
    }
}

/******************************************************************************
* Function to save the current color map and a batch file that should be      *
* invoked to create a gif image out of the raw data saved.		      *
******************************************************************************/
static void SaveColorMap(int FileNumber)
{
    char FileName[20];
    int i, Handle;
    union REGS InRegs, OutRegs;

    sprintf(FileName, SAVE_FILENAME_CMAP, FileNumber);
    if ((Handle = _creat(FileName, 0)) != -1) {
	if (Device == HERCULES || Device == CGA_HI) {
	    /* Black and White color map. */
	    _write(Handle, "1   0   0   0\r\n", 15);
	    _write(Handle, "2 255 255 255\r\n", 15);
	}
	else if (Device == CGA_LOW) {
	    /* 4 color map (palette 1 only). */
	    _write(Handle, "1   0   0   0\r\n", 15);
	    _write(Handle, "2   0 255   0\r\n", 15);
	    _write(Handle, "3 255   0   0\r\n", 15);
	    _write(Handle, "4   0 255 255\r\n", 15);
	}
	else {
	    for (i = 0; i < NumOfColors; i++) {
	        InRegs.h.bl = i;
	        InRegs.x.ax = 0x1015;
	        int86(0x10, &InRegs, &OutRegs);
	        sprintf(ScanLine, "%3d %4d %4d %4d\r\n",
            	    i + 1,
                    OutRegs.h.dh << 2, OutRegs.h.ch << 2, OutRegs.h.cl << 2);
	        _write(Handle, ScanLine, strlen(ScanLine));
	    }
	}
	_close(Handle);
    }

    sprintf(FileName, SAVE_FILENAME_BAT, FileNumber);
    if ((Handle = _creat(FileName, 0)) != -1) {
	sprintf(ScanLine, "raw2gif -s %d %d -p "
        		  SAVE_FILENAME_CMAP " "
                          SAVE_FILENAME_RAW " > "
                          SAVE_FILENAME_GIF "\r\n",
                          DeviceXMax, DeviceYMax,
                          FileNumber, FileNumber, FileNumber);
	_write(Handle, ScanLine, strlen(ScanLine));
	sprintf(ScanLine, "del "
        		  SAVE_FILENAME_CMAP "\r\ndel "
                          SAVE_FILENAME_RAW "\r\n",
                          FileNumber, FileNumber);
	_write(Handle, ScanLine, strlen(ScanLine));
	_close(Handle);
    }

}

/******************************************************************************
* This is the function get invoked when TSR gets activated.		      *
******************************************************************************/
static void TsrFunc(void)
{
    static int FileNumber = 1;
    char FileName[20];
    int i, Handle;

    SetDeviceParams();

    SaveColorMap(FileNumber);

    sprintf(FileName, SAVE_FILENAME_RAW, FileNumber++);
    if ((Handle = _creat(FileName, 0)) != -1) {
	for (i = 0; i < DeviceYMax; i++) {
	    GetScanLine(ScanLine, i);
	    _write(Handle, ScanLine, DeviceXMax);
	}
	_close(Handle);
    }
}
