/*****************************************************************************
* Project:  Workstation inventory
* File:		INVENTRY.C
* Author:	Morgan B. Adair
* Date:		12/15/91
*****************************************************************************/

/********** includes *********/

#include <stdio.h>
#include <stdlib.h>
#include <mem.h>
#include <bios.h>
#include <dos.h>
#include <string.h>
#include <nit.h>
#include <niterror.h>
#include <nxt.h>
#include <diag.h>
#include "recdecl.h"
#include "btrieve.h"
#include "datafile.h"

/********** defines *********/

#define PROGRAM_NAME "INVENTRY"

#define NUM_PRINTERS		0xC000
#define NUM_SERIAL_PORTS	0x0E00
#define NUM_FLOPPY_DRIVES	0x00C0
#define MATH_COPROC		0x0002
#define HAS_FLOPPIES		0x0001
#define CARRY_FLAG		0x0001
#define MOUSE_DRVR_MAJOR_VER	0xFF00
#define MOUSE_DRVR_MINOR_VER	0x00FF
#define IGNORE			0
#define NOT_READY		2

/********** typedefs *********/

typedef struct SYS_DESC_TABLE {
	WORD	length;
	BYTE	model;
	BYTE	submodel;
	BYTE	BIOSrevisionLevel;
	BYTE	featureFlags;
	BYTE	reserved[4];
} SYS_DESC_TABLE;

/********** function prototypes *********/

extern int cputype(void);

int GetNetworkData(BYTE *network, BYTE *node,
		   char *userName,
		   BYTE *IPXmajorVersion,
		   BYTE *IPXminorVersion,
		   BYTE *SPXmajorVersion,
		   BYTE *SPXminorVersion,
		   BYTE *shellMajorVersion,
		   BYTE *shellMinorVersion,
		   BYTE *shellRevisionLevel);
void GetMachineModel(BYTE *machineModel, BYTE *machineSubmodel);
void GetProcessorType(WORD *processorType);
void GetCoprocessorType(WORD *coprocessorType);
void GetConventionalMemory(int *conventionalMemoryK);
void GetExtendedMemory(WORD *extendedMemoryK);
void GetExpandedMemory(WORD *expandedMemoryK);
int EMMInstalled(void);
void GetVideoHardware(int *videoHardware, int *alternateVideoHardware);
void GetDOSVersion(BYTE *DOSversionMajor, BYTE *DOSversionMinor);
void GetNICType(BYTE *NICtype);
void GetOtherEquipment(BYTE *numPrintersInstalled,
		       BYTE *numSerialPorts,
		       BYTE *numFloppyDrives);
void GetMouse(BYTE *mouseType, WORD *mouseDriverVersion);
void GetFloppyDriveTypes(BYTE numFloppyDrives, BYTE *floppyDriveType);
void GetHardDisks(BYTE *numHardDrives, LONG *hardDriveSize);
void DisplayType(int displayCode);

/********** global vars *********/

int	verbose = 0;

void main(int argc, char **argv)
{
	int			status;
	int			iLoadedBtrieve = 0;
	WS_INVENTORY_RECORD	inventoryRecord;
	char			dbPath[80] = "\\PUBLIC\\INVENTRY.BTV";

	printf("%s: network workstation hardware inventory\n", PROGRAM_NAME);

	if (argc > 3) {
		printf("Usage:\n");
		printf("\t%s [-v] [path]\n", PROGRAM_NAME);
		printf("\t-v     (verbose) Echo hardware description on workstation screen\n");
		printf("\tpath   Path for database file\n");
		printf("\t       Default path is %s\n", dbPath);
	} else if (argc > 1)
		while (argc > 1) {
			argc--;

			if (stricmp(argv[argc], "-v") == 0)
				verbose = TRUE;

			else
				strcpy(dbPath, argv[argc]);
		}

	if (!(BtrieveIsLoaded())) {
		LoadBtrieve();
		if (!(BtrieveIsLoaded())) {
			printf("Unable to load Btrieve record manager\n");
			exit(1);
		}
		iLoadedBtrieve = TRUE;
	}

	if (!(DataFileExists(dbPath))) {
		status = CreateDataFile(dbPath);
		if (status) {
			printf("Unable to create database file, Btrieve error code %d\n", status);
			exit(1);
		}
	}

	status =  OpenDataFile(dbPath);
	if (status) {
		printf("Unable to open file %s\n", dbPath);
		printf("Btrieve error code %d\n", status);
		exit(1);
	}

	/* zero out inventory record */
	memset(&inventoryRecord, 0, sizeof(WS_INVENTORY_RECORD));

	status = GetNetworkData(inventoryRecord.network,
				inventoryRecord.node,
				inventoryRecord.userName,
				&inventoryRecord.IPXmajorVersion,
				&inventoryRecord.IPXminorVersion,
				&inventoryRecord.SPXmajorVersion,
				&inventoryRecord.SPXminorVersion,
				&inventoryRecord.shellMajorVersion,
				&inventoryRecord.shellMinorVersion,
				&inventoryRecord.shellRevisionLevel);
	if (status != SUCCESSFUL)
		printf("Network not loaded\n");

	GetMachineModel(&inventoryRecord.machineModel, &inventoryRecord.machineSubmodel);

	GetProcessorType(&inventoryRecord.processorType);

	GetCoprocessorType(&inventoryRecord.coprocessorType);

	GetConventionalMemory(&inventoryRecord.conventionalMemoryK);

	GetExtendedMemory(&inventoryRecord.extendedMemoryK);

	GetExpandedMemory(&inventoryRecord.expandedMemoryK);

	GetVideoHardware(&inventoryRecord.videoHardware, &inventoryRecord.alternateVideoHardware);

	GetDOSVersion(&inventoryRecord.DOSversionMajor, &inventoryRecord.DOSversionMinor);

	GetNICType(inventoryRecord.NICtype);

	GetOtherEquipment(&inventoryRecord.numPrintersInstalled,
			  &inventoryRecord.numSerialPorts,
			  &inventoryRecord.numFloppyDrives);

	GetMouse(&inventoryRecord.mouseType, &inventoryRecord.mouseDriverVersion);

	GetFloppyDriveTypes(inventoryRecord.numFloppyDrives, &inventoryRecord.floppyDriveType[0]);

	GetHardDisks(&inventoryRecord.numHardDrives, &inventoryRecord.hardDriveSize[0]);

	status = SearchItem(&inventoryRecord);
	if (status == BT_SUCCESS)
		status = UpdateItem(&inventoryRecord);
	else
		status = InsertItem(&inventoryRecord);

	if (status) {
		printf("Unable to add inventory record\n");
		printf("Btrieve error code %d\n", status);
	}

	status = CloseDataFile();
	if (status) {
		printf("Unable to close inventory data file\n");
		printf("Btrieve error code %d\n", status);
	}

	if (iLoadedBtrieve) {
		status = UnloadBtrieve();
		if (status) {
			printf("Unable to unload Btrieve record manager\n");
			printf("Btrieve error code %d\n", status);
		}
	}
}

int GetNetworkData(BYTE *network, BYTE *node,
		   char *userName,
		   BYTE *IPXmajorVersion,
		   BYTE *IPXminorVersion,
		   BYTE *SPXmajorVersion,
		   BYTE *SPXminorVersion,
		   BYTE *shellMajorVersion,
		   BYTE *shellMinorVersion,
		   BYTE *shellRevisionLevel)
{
	BeginDiagnosticStruct	networkAddress;
	int			ccode;
	BYTE			componentList[54];
	int			component;
	AllResponseData		response;
	IPXSPXVersion		ipxSpxData;
	WORD			connectionID;
	ShellVersionStruct	shellVerData;
	WORD			connectionNumber;

	/* Get network and node addresses */
	/* make sure there is a network down there */ 
	if (IPXInitialize() == IPX_NOT_INSTALLED)
		return(~SUCCESSFUL);

	/* get network address */
	IPXGetInternetworkAddress((BYTE *)&networkAddress);

	/* copy to inventory record */
	memcpy(network, networkAddress.network, 4);
	memcpy(node, networkAddress.node, 6);

	/* Get IPX/SPX versions */
	/* Get diagnostics component list */
	ccode = BeginDiagnostics(&networkAddress, &connectionID, componentList);
	if (ccode != SUCCESSFUL) {
		if (verbose)
			printf("Unable to get IPX/SPX versions\n");
	} else {
		ccode = SPXInitialize(NULL, NULL, NULL, NULL);
		if (ccode == SPX_NOT_INSTALLED) {
			if (verbose)
				printf("SPX is not loaded\n");
			return(~SUCCESSFUL);
		}

		/* find the offset of the IPX/SPX component in the component list */
		component = FindComponentOffset(componentList, IPX_SPX_COMPONENT);
		if (component == -1) {
			if (verbose)
				printf("Unable to get IPX/SPX versions\n");
			return(~SUCCESSFUL);
		
		} else {
			/* get the IPX/SPX data from the diagnostics components */
			if (GetIPXSPXVersion(connectionID, component, &response, &ipxSpxData) != SUCCESSFUL) {
				if (verbose)
					printf("Unable to get IPX/SPX versions\n");
				return(~SUCCESSFUL);
			} else {
				*IPXmajorVersion = ipxSpxData.IPXMajorVersion;
				*IPXminorVersion = ipxSpxData.IPXMinorVersion;
				*SPXmajorVersion = ipxSpxData.SPXMajorVersion;
				*SPXminorVersion = ipxSpxData.SPXMinorVersion;

				if (verbose) {
					printf("IPX Version: %d.%02d\n", *IPXmajorVersion, *IPXminorVersion);
					printf("SPX Version: %d.%02d\n", *SPXmajorVersion, *SPXminorVersion);
				}
			}
		}

		/* Get shell version */
		component = FindComponentOffset(componentList, SHELL_COMPONENT);
		if (component == -1) {
			if (verbose)
				printf("Unable to get shell version\n");
			return(~SUCCESSFUL);

		} else {
			ccode = GetShellVersionInfo(connectionID, component, &response, &shellVerData);
			if (ccode != SUCCESSFUL) {
				if (verbose)
					printf("Unable to get shell version\n");
				return(~SUCCESSFUL);
			} else {
				*shellMajorVersion = shellVerData.major;
				*shellMinorVersion = shellVerData.minor;
				*shellRevisionLevel = shellVerData.rev;

				if (verbose)
					printf("Shell Version: %d.%d, Rev %c\n", *shellMajorVersion,
										 *shellMinorVersion,
										 *shellRevisionLevel+'A');
			}
		}
	}
	EndDiagnostics(connectionID);

	/* Get user name on default server */
	connectionNumber = GetConnectionNumber();
	ccode = GetConnectionInformation(connectionNumber, userName, (WORD *)NULL, (long *)NULL, (BYTE *)NULL);
	if (verbose)
		printf("User name (default server): %s\n", userName);

	if (!ccode)
		return(SUCCESSFUL);
	else
		return(~SUCCESSFUL);
}

void GetMachineModel(BYTE *machineModel, BYTE *machineSubmodel)
{
	SYS_DESC_TABLE far 	*systemDescriptorTable;

	/* clear carry flag */
	_FLAGS = _FLAGS & ~CARRY_FLAG;

	/* get system configuration: interrupt 15h, function 0Ch */
	asm	mov	ax,0C000h
	asm	int	15h

	if ((_AH == 0) && !(_FLAGS & CARRY_FLAG)) {
		systemDescriptorTable = MK_FP(_ES, _BX);

		*machineModel = systemDescriptorTable->model;
		*machineSubmodel = systemDescriptorTable->submodel;

		if (verbose) {
			switch (systemDescriptorTable->model) {
				case	0x2D	:	printf("Compaq Portable\n");
							break;
				case	0x9A	:	printf("Compaq Portable Plus\n");
							break;
				case	0xF8	:	printf("IBM PS/2 Model 80\n");
							break;
				case	0xF9	:	printf("IBM PC Convertible\n");
							break;
				case	0xFA	:	printf("IBM PS/2 Model 30\n");
							break;
				case	0xFB	:	printf("IBM PC XT\n");
							break;
				case	0xFC	:	switch (systemDescriptorTable->submodel) {
								case	0	:
								case	1	:	printf("IBM PC/AT\n");
											break;
								case	2	:	printf("IBM PC/XT286\n");
											break;
								case	4	:	printf("IBM PS/2 Model 50\n");
											break;
								case	5	:	printf("IBM PS/2 Model 60\n");
											break;
								case	0xB	:	printf("IBM PS/1\n");
											break;
								default		:	printf("IBM PC/AT or PS class\n");
											break;
							}
							break;
				case	0xFD	:	printf("IBM PCjr\n");
							break;
				case	0xFE	:	printf("IBM PC/XT, Portable PC, or Compaq DeskPro\n");
							break;
				case	0xFF	:	printf("IBM PC\n");
							break;
			}
		}
	}
}

void GetProcessorType(WORD *processorType)
{
	*processorType = cputype();

	if (verbose) {
		switch (*processorType) {
			case 	0x0086	:	printf("8086 or 8088 processor\n");
						break;
			case 	0x0286	:	printf("80286 processor\n");
						break;
			case 	0x0386	:	printf("80386DX or 80386SX processor\n");
						break;
			case 	0x0486	:	printf("80486DX or 80486SX processor\n");
						break;
			default		:	printf("Error identifying CPU\n");
						break;
		}
	}
}

void GetCoprocessorType(WORD *coprocessorType)
{
	switch (_8087) {
		case	0	:	*coprocessorType = 0;
					break;
		case	1	:	*coprocessorType = 0x0087;
					break;
		case	2	:	*coprocessorType = 0x0287;
					break;
		case	3	:	*coprocessorType = 0x0387;
					break;
		default		:	*coprocessorType = 0xFFFF;
					break;
	}

	if (verbose) {
		switch (*coprocessorType) {
			case	0	:	printf("No coprocessor\n");
						break;
			case	0x0087	:	printf("8087 coprocessor\n");
						break;
			case	0x0287	:	printf("80287 coprocessor\n");
						break;
			case	0x0387	:	printf("80387 coprocessor\n");
						break;
			default		:	printf("Error getting coprocessor type: %u\n", coprocessorType);
						break;
		}
	}
}

void GetConventionalMemory(int *conventionalMemoryK)
{
	*conventionalMemoryK = biosmemory();

	if (verbose)
		printf("%dK base memory\n", *conventionalMemoryK);
}

void GetExtendedMemory(WORD *extendedMemoryK)
{
	int far (*himemEntry)();	/* entry point of HIMEM.SYS driver */

	/* see if HIMEM.SYS is installed */
	asm	mov	ax,4300h
	asm	int	2Fh

	if (_AL == 0x80) {

		/* HIMEM.SYS installed.  Get entry point of himem driver */
		asm	mov	ax,4310h
		asm	int	2fh

		himemEntry = MK_FP(_ES, _BX);

		/* call himem driver function 8: get extended memory size */
		asm	mov	ah,08h

		himemEntry();

		*extendedMemoryK = _DX;

	} else {
		/* HIMEM.SYS is not installed, use BIOS function to get extended memory */
		/* clear carry flag */
		_FLAGS = _FLAGS & ~CARRY_FLAG;

		/* get extended memory size: interrupt 15h, function 88h */
		asm	mov	ah,88h
		asm	int	15h

		if ((_FLAGS & CARRY_FLAG) != 0)
			*extendedMemoryK = 0;
		else
			*extendedMemoryK = _AX;
	}
	if (verbose)
		printf("%dK extended memory\n", *extendedMemoryK);
}

void GetExpandedMemory(WORD *expandedMemoryK)
{
	if (EMMInstalled()) {
		/* get expanded memory page count:
		   interrupt 67h, function 42h */
		asm	mov	ah,42h
		asm	int	67h

		if (_AH == 0)
			*expandedMemoryK = _DX * 16;
	}
		if (verbose)
			printf("%dK expanded memory\n", *expandedMemoryK);
}

int EMMInstalled(void)
{
	char far	*deviceName;

	/* get segment of EMM device driver by calling
	   get interrupt vector: interrupt 21h, function 35h */
	asm	mov	al,67h
	asm	mov	ah,35h
	asm	int	21h

	deviceName = MK_FP(_ES, 10);	/* address of character device driver name */

	/* see if the name of the EMM device driver is where it should be when driver is loaded */
	if (_fmemcmp("EMMXXXX0", deviceName, 8))
		return(FALSE);
	else
		return(TRUE);	/* if all characters matched, EMM is present */
}

void GetVideoHardware(int *videoHardware, int *alternateVideoHardware)
{
	/* read display codes: interrupt 10h, function 1Ah, subfunction 0 */
	asm	mov	ax,1A00h
	asm	int	10h

	if (_AL != 0x1A) {
		if (verbose)
			printf("Unable to identify graphics hardware\n");

	} else {
		*videoHardware = _BL;
		*alternateVideoHardware = _BH;

		if (verbose) {
			DisplayType(*videoHardware);

			/* if there is an alternate display, find out what type it is */
			if (*alternateVideoHardware) {
				printf("Alternate display: ");
				DisplayType(*alternateVideoHardware);
			}
		}
	}
}

void GetDOSVersion(BYTE *DOSversionMajor, BYTE *DOSversionMinor)
{
	*DOSversionMajor = _osmajor;
	*DOSversionMinor = _osminor;

	if (verbose)
		printf("DOS version %d.%02d\n", _osmajor, _osminor);
}

void GetNICType(BYTE *NICtype)
{
	BeginDiagnosticStruct		networkAddress;
	int				ccode;
	BYTE				componentList[54];
	int				component;
	AllResponseData			response;
	DriverConfigurationStruct	driverData;
	WORD				connectionID;

	/* do network diagnostics, same as we did for IPX/SPX/shell */
	IPXGetInternetworkAddress((BYTE *)&networkAddress);

	ccode = BeginDiagnostics(&networkAddress, &connectionID, componentList);
	if ((ccode != SUCCESSFUL) && (verbose)) {
		printf("Unable to get network card type\n");
	} else {
		component = FindComponentOffset(componentList, SHELL_DRIVER_COMPONENT);
		if ((component == -1) && (verbose)) {
			printf("Unable to get network card type\n");
		} else {
			if ((GetShellDriverConfiguration(connectionID, component, &response, &driverData) != SUCCESSFUL) &&
			    (verbose)) {
				printf("Unable to get network card type\n");
			} else {
				strcpy((char *)NICtype, (char *)driverData.LANDescription);
				if (verbose)
					printf("%s\n", NICtype);
			}
		}
	}
	EndDiagnostics(connectionID);
}

void GetOtherEquipment(BYTE *numPrintersInstalled,
		       BYTE *numSerialPorts,
		       BYTE *numFloppyDrives)
{
	int	equipFlags;

	equipFlags = biosequip();

	if (equipFlags & HAS_FLOPPIES)
		*numFloppyDrives = ((equipFlags & NUM_FLOPPY_DRIVES) >> 6) + 1;
	*numPrintersInstalled = (equipFlags & NUM_PRINTERS) >> 14;
	*numSerialPorts = (equipFlags & NUM_SERIAL_PORTS) >> 9;

	if (verbose) {
		printf("%d floppy drives\n", *numFloppyDrives);
		printf("%d printers installed\n", *numPrintersInstalled);
		printf("%d serial ports\n", *numSerialPorts);
	}
}

void GetMouse(BYTE *mouseType, WORD *mouseDriverVersion)
{
	/* mouse status: interrupt 33h, function 0 */
	asm	mov	ax,0h
	asm	int	33h

	if (_AX) {
		/* mouse installed */
		/* get mouse driver version and mouse type:
		   interrupt 33h, function 24h */
		asm	mov	ax,24h
		asm	int	33h

		*mouseType = _CH;
		*mouseDriverVersion = _BX;

		if (verbose) {
			printf("Mouse driver version: %d.%02d\n",
				(*mouseDriverVersion & MOUSE_DRVR_MAJOR_VER) >> 8,
				 *mouseDriverVersion & MOUSE_DRVR_MINOR_VER);

			switch (*mouseType) {
				case 1	:	printf("Bus mouse\n");
						break;
				case 2	:	printf("Serial mouse\n");
						break;
				case 3	:	printf("InPort mouse\n");
						break;
				case 4	:	printf("PS/2 mouse\n");
						break;
				case 5	:	printf("Hewlet-Packard mouse\n");
						break;
				default	:	printf("Unknown mouse type\n");
						break;
			}
		}
	}
}

void GetFloppyDriveTypes(BYTE numFloppyDrives, BYTE *floppyDriveType)
{
	BYTE	i;
	BYTE	driveTypeTemp;

	for (i=0; i<numFloppyDrives; i++) {
		/* read drive parameters: interrupt 13h, function 8 */
		asm	mov	ah,08h
		asm	mov	dl,i
		asm	int	13h

		driveTypeTemp = _BL;		/* use temp variable, because
						   assignment directly to the
						   array changes _BL */
		floppyDriveType[i] = driveTypeTemp;

		if (verbose) {
			printf("Floppy drive %d: ", i);
			switch (floppyDriveType[i]) {
				case	1	:	printf("360KB\n");
							break;
				case	2	:	printf("1.2MB\n");
							break;
				case	3	:	printf("720KB\n");
							break;
				case	4	:	printf("1.44MB\n");
							break;
				default		:	printf("unable to identify type\n");
							break;
			}
		}
	}
}

void GetHardDisks(BYTE *numHardDrives, LONG *hardDriveSize)
{
	BYTE		driveNumber;
	LONG		freeBytes, bytesPerCluster;
	struct dfree	diskFreeInfo;

	driveNumber = 3;
		/* assume drives 1 & 2 (A: & B:), if they
		   exist, are mapped to floppy drives */
	while ((driveNumber <= 26) && (*numHardDrives <= MAX_HARD_DRIVES)) {

		/* clear carry flag */
		_FLAGS = _FLAGS & ~CARRY_FLAG;

		/* skip if network drive */
		asm	mov	ah,44h
		asm	mov	al,09h
		asm	mov	bl,driveNumber
		asm	int	21h

		if ((!(_FLAGS & CARRY_FLAG)) &&		/* carry flag set on errror */
		    (!(_DX & 0x1000))) {		/* bit 12 of dx is set if remote */

			getdfree(driveNumber, &diskFreeInfo);
			if (diskFreeInfo.df_sclus != 0xFFFF) {
				bytesPerCluster = (LONG)diskFreeInfo.df_sclus * (LONG)diskFreeInfo.df_bsec;
				hardDriveSize[*numHardDrives] = (LONG)diskFreeInfo.df_total * bytesPerCluster;
				freeBytes = (LONG)diskFreeInfo.df_avail * bytesPerCluster;

				if (verbose)
					printf("Drive %c: %ld bytes free out of %ld bytes\n",
						driveNumber+'A'-1, freeBytes, hardDriveSize[*numHardDrives]);

				*numHardDrives = *numHardDrives+1;
			}
		}
		driveNumber++;
	}
}
