/*
 * first.S86  -  primary boot loader for DOS
 *
 * Copyright (C) 1996-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: first.S86,v 1.17 2007/01/07 16:08:56 gkminix Exp $
 *
 *====================================================================
 */
#define DEF_STACK_SIZE 1
#define DEF_HEAP_SIZE 1

#include <common.i86>
#include <netboot.i86>
#include <net/bootp.i86>
#include <system/nbloader.i86>
#include <system/bios.i86>
#include <system/partition.i86>
#include "headers/boot.i86"
#include "headers/fat.i86"
#include "headers/dos.i86"
#include "headers/rd.i86"
#include "first.i86"

.file __FILE__
.line __LINE__



/*
 * Care for debugging symbols
 */
.ifndef ASM_DEBUG
	ASM_DEBUG = 0
.endif
.ifndef DRV_DEBUG
	DRV_DEBUG = 0
.endif
.ifndef ASM_FREEZE_AFTER_INIT
	ASM_FREEZE_AFTER_INIT = 0
.endif
.ifndef ASM_KEYPRESS_AFTER_INIT
	ASM_KEYPRESS_AFTER_INIT = 0
.endif



/*
 *====================================================================
 *
 * Define stack and heap size
 */

/* Stack size is common for all netboot image loader programs */
__stack_size = MIN_STK_SIZE


/* We dont need a heap */
__heap_size = 0




/*
 *====================================================================
 *
 * Definitions which are local to this module
 */
#define HMA_SIZE	65		/* size of HMA in kB */

#define I13_INT		(0x13 * 4)	/* interrupt vector 13h */
#define I15_INT		(0x15 * 4)	/* interrupt vector 15h */
#define I2F_INT		(0x2F * 4)	/* interrupt vector 2Fh */
#define IF1_INT		(0xF1 * 4)	/* interrupt vector F1h */
#define IF8_INT		(0xF8 * 4)	/* interrupt vector F8h */

#define COMPAQ_SEG	0xF000		/* segment and offset of Compaq */
#define COMPAQ_OFS	0xFFE0		/* signature */
#define COMPAQ_LEN	6		/* length of signature */



.line __LINE__
/*
 *====================================================================
 *
 * Define some external routines from the i386 library
 */
	.text

	.extern	nbentry			# routines in nbentry.s86
	.extern	doerr
	.extern	nopxe

	.extern	ldrlen			# routines in ldrec.s86
	.extern	ldradr
	.extern	ldrfnd

	.extern	prnstr			# routines in scnio.s86
	.extern	prcrlf
	.extern	prndwd
	.extern	prnwrd
	.extern	prnbyt
	.extern	prnchr

	.extern	lzo1x_decompress	# routines in lzo1x.S86

	.extern	getext			# routines in misc.s86
	.extern	getmem
	.extern	movmem



/*
 *====================================================================
 *
 * Boot image loader header
 */

	jmp	nopxe			# PXE entry point

	.org	NBLD_OFFSET

nbld_header:
	.word	__text_end		# total size of text segment
	.word	__bss_end		# total size of data segment
	.word	__heap_size		# minimum heap size
	.word	__stack_size		# minimum stack size
	.word	nbentry			# boot image entry routine
	.word	start			# the main program
	.word	vmagic			# vendor ID
	.word	sigmsg			# copyright message
	.word	0			# no user information



/*
 *====================================================================
 *
 * Now start the game
 */

start:

.ifgt ASM_DEBUG
	int	0x03			# debugger hook
.endif

	call	getext			# get the extended memory size
	mov	cs:[extsiz],eax

/*
 * Find the load record for this boot loader and get the vendor flags.
 */

	mov	si,offset recerr
	mov	al,LOADERNUM + LOADER_TAG
	call	ldrfnd			# find load record for boot loader
	jc	doerr
	mov	bl,fs:boot_ld_length[di]
	test	bl,0xF0			# get offset of vendor information
	jz	doerr			# within load record
	and	bx,0x000F
	shl	bx,2			# get boot loader flags
	mov	al,fs:boot_ld_loadflags[bx + di]
	mov	cs:[ldflags],al

	call	ldradr			# get base address of loader
	jc	doerr
	add	eax,fs:boot_ld_mlength[di]
	add	eax,0x0000000F
	test	eax,0xFFF00000		# must be within base memory
	jnz	doerr
	shr	eax,4			# compute first free segment
	mov	[freeseg],ax

/*
 * Next get the address of the ramdisk and its size.
 * When starting DOS we have to make sure that the ramdisk doesnt get
 * overwritten by someone. If an XMS driver like HIMEM.SYS gets loaded,
 * we can protect the ramdisk using it. However, when we are not able
 * or dont want to use XMS, we have to use some other means, and this
 * is by using the interrupt 15h method. We redirect interrupt 15h so
 * that it returns an extended memory size reduced by the ramdisk size.
 * However, for this to work, the ramdisk has to be at the end of the
 * physical memory. Check that the bootrom correctly moved the ramdisk
 * at the end of the physical memory area in this case.
 */

	mov	si,offset recerr
	mov	al,RAMDISKNUM + LOADER_TAG
	call	ldrfnd			# find load record for ramdisk
	jc	doerr
	call	ldradr			# get base address of ramdisk
	jc	doerr
	test	eax,0xFFF00000		# must be within extended memory
	jz	doerr
	mov	cs:[rdaddr],eax		# save ramdisk address

	mov	edx,fs:boot_ld_ilength[di]
	mov	ecx,fs:boot_ld_mlength[di]
	test	ecx,0x000003FF		# must be a multiple of 1024
	jnz	doerr
	cmp	ecx,edx			# ramdisk image should not exceed
	jb	doerr			# total ramdisk size
	shr	ecx,10			# get ramdisk size in kB
	cmp	ecx,MAX_RDSIZE		# ramdisk should not be larger
	jae	doerr			# than 64 MB
	mov	cs:[rdsize],ecx
	add	edx,0x000003FF
	shr	edx,10			# round image size to next full kB
	mov	[imgsize],edx
	shl	ecx,10
	add	ecx,eax			# compute end address of ramdisk
	add	ecx,0x000003FF		# round up to next full kB
	shr	ecx,10			# compute number of kB
	sub	ecx,0x00000400		# subtract conventional memory
	cmp	ecx,cs:[extsiz]		# check if ramdisk exceeds max.
	ja	doerr			# amount of physical memory

	test	byte ptr cs:[ldflags],BOOT_FLAG_INT15
	jz	3f			# check using int 15 method
	shr	eax,10			# convert address into kB
	sub	eax,0x00000400		# subtract conventional memory
	jc	doerr
	mov	cs:[extsiz],eax		# save it for later

3:
.ifgt ASM_DEBUG
	mov	si,offset rdmsg
	call	prnstr
	mov	eax,cs:[rdaddr]
	call	prndwd			# let the user know
	mov	al,0x28
	call	prnchr
	mov	eax,[imgsize]
	shl	eax,10
	call	prndwd
	mov	al,0x2C
	call	prnchr
	mov	eax,cs:[rdsize]
	shl	eax,10
	call	prndwd
	mov	ax,0x29
	call	prnchr
	call	prcrlf
.endif

/*
 * Get the disk geometry out of the vendor information block in the
 * load record
 */

	mov	si,offset geoerr
	mov	bl,fs:boot_ld_length[di]
	test	bl,0xF0
	jz	doerr
	and	bx,0x000F		# get offset of vendor infor-
	shl	bx,2			# mation within load record

	mov	eax,fs:boot_ld_secnum[bx + di]	# get number of sectors
	cmp	eax,MAX_CYLS * MAX_SECTS * MAX_HEADS
	jae	doerr
	mov	cs:[secnum],eax

	mov	ax,fs:boot_ld_spt[bx + di]	# get sectors per track
	cmp	ax,MAX_SECTS
	ja	doerr
	mov	cs:[secptk],ax

	mov	ax,fs:boot_ld_cyl[bx + di]	# get number of cylinders
	cmp	ax,MAX_CYLS
	ja	doerr
	mov	cs:[cylnum],ax

	mov	ax,fs:boot_ld_hdnum[bx + di]	# get number of heads
	cmp	ax,MAX_HEADS
	ja	doerr
	mov	cs:[hdnum],ax

	mov	al,fs:boot_ld_nohd[bx + di]	# get no-hard-disk flag
	mov	cs:[nohd],al
	mov	al,fs:boot_ld_drive[bx + di]	# get ram disk drive id
	cmp	al,DISKID_FLOPPY
	je	2f
	cmp	al,DISKID_HD
	jne	doerr
2:	mov	cs:[drvid],al

/*
 * Determine the BIOS drive type according to the total number of
 * sectors in ramdisk.
 */

	push	ax
	xor	dl,dl
	cmp	al,DISKID_HD			# drive type zero for
	jae	5f				# hard disk or for disks
	test	word ptr cs:[secnum + 2],0xFFFF	# which are too big
	jnz	5f

	cld
	mov	si,offset typtab
4:	inc	dl
	lodsw					# get next sector number
	cmp	ax,-1
	je	4b
	cmp	ax,word ptr cs:[secnum + 0]	# save drive type number
	je	5f
	or	ax,ax				# check if at end of table
	jnz	4b
	xor	dl,dl
5:	mov	cs:[drvtyp],dl
	pop	ax

/*
 * Set the address of the BIOS disk status byte
 */

	mov	bx,BIOS_FDSTAT
	cmp	al,DISKID_HD
	jb	1f
	mov	bx,BIOS_HDSTAT
1:	mov	cs:[statof],bx

/*
 * Get the number of floppy or hard disk drives in the system. Note that
 * function 0x08 of interrupt 0x13 can return an invalid number of drives
 * on certain machines. See Ralf Browns interrupt list for further info.
 */

	mov	cx,BIOS_SEG
	mov	gs,cx
	mov	cx,gs:[BIOS_SYSCNF]	# get system configuration word
	mov	cs:[syscnf],cx		# from BIOS data area
	cmp	al,DISKID_HD
	jae	2f

	test	cl,0x01			# if at least one floppy drive is
	jnz	4f			# installed dont touch the BIOS
	or	cl,0x01			# configuration word
	and	cl,0x3F			# otherwise indicate one floppy drive
	mov	gs:[BIOS_SYSCNF],cx	# in system configuration word
4:	mov	ah,0x08
	xor	cl,cl
	xor	dl,dl
	xor	di,di			# required because of bugs in certain
	mov	es,di			# BIOS when calling func 08 of int 13
	int	INT_DISK		# get the number of floppy disk
	jc	1f			# drives from the BIOS
	or	cl,cl			# dont trust the result if the number
	jz	1f			# of sectors is zero (BIOS bugs)
	or	dl,dl
	jnz	5f
	inc	dl			# indicate at least one drive
	jmp	5f
1:	mov	dx,cs:[syscnf]		# if int 13h didnt work try it with
	test	dl,0x01			# the mainboard dip switch config
	jz	3f
	shr	dl,6
	and	dl,0x03			# determine number of floppy disk
	inc	dl			# drives
	jmp	5f

2:	cld
	mov	ax,COMPAQ_SEG
	mov	es,ax
	mov	di,COMPAQ_OFS		# check if we have a Compaq system
	mov	si,offset compaq
	mov	cx,COMPAQ_LEN
	repe	cmpsb
	jne	6f

	mov	ax,0xE400
	int	INT_MISC		# get advanced system info
	jc	6f
	or	bx,0x0010		# set hard disk "mode 2" on
	mov	ax,0xE480		# Compaq system to get the real
	int	INT_MISC		# number of hard disks

6:	xor	di,di
	mov	es,di
	mov	ah,0x08
	mov	dl,DISKID_HD
	int	INT_DISK		# get the number of hard disk
	jc	3f			# drives from the BIOS
	inc	dl
	jmp	5f

3:	mov	dl,1			# we have at least one drive
5:	mov	cs:[drvnum],dl		# save number of disk drives

/*
 * Save the BIOS disk parameter block for the first floppy drive. This is
 * necessary so that our own boot block can use it as scratch area.
 */

	cld
	push	ds
	mov	ax,cs
	mov	es,ax
	xor	ax,ax			# save old BIOS disk parameter block
	mov	ds,ax
	lds	si,ds:[DPB_VECT]
	mov	di,offset new_dpb
	mov	cx,DPB_SIZE
	rep	movsb
	pop	ds

	mov	ax,cs:[secptk]		# prepare DPB for boot sector
	mov	byte ptr cs:dpb_sptrack[new_dpb],al
	mov	byte ptr cs:dpb_headsettle[new_dpb],0x0f

/*
 * All ramdisk parameters have been setup by now. Check if the ramdisk
 * image is compressed, and decompress it if necessary. For this we
 * need at least 128kB of buffer memory.
 */

	test	byte ptr cs:[ldflags],BOOT_FLAG_COMPR
	jz	4f
	mov	si,offset memerr
	mov	ax,[stktop]
	add	ax,0x000F		# compute first segment following
	shr	ax,4			# our data segment
	mov	bx,ds
	add	ax,bx
	mov	bx,[freeseg]		# compute amount of free memory
	sub	bx,ax
	jc	doerr
	cmp	bx,0x2000		# check that we have enough memory
	jb	doerr			# for decompression buffers
	mov	[hdrseg],ax		# save segment for image header
	add	ax,0x1000
	mov	[bufseg],ax		# save segment for decompression buffer

	mov	si,offset decmsg	# print decompression message
	call	prnstr
	call	decomp			# actually decompress the ramdisk
	jnc	3f			# image
	call	prcrlf
	mov	si,offset decerr	# print error message
	jmp	doerr

3:	mov	si,offset donmsg	# let the user know that we are done
	call	prnstr			# decompressing
	call	prcrlf

.ifgt ASM_DEBUG
	mov	si,offset rdmsg
	call	prnstr
	mov	eax,cs:[rdaddr]
	call	prndwd			# let the user know
	mov	al,0x28
	call	prnchr
	mov	eax,[imgsize]
	shl	eax,10
	call	prndwd
	mov	al,0x2C
	call	prnchr
	mov	eax,cs:[rdsize]
	shl	eax,10
	call	prndwd
	mov	ax,0x29
	call	prnchr
	call	prcrlf
.endif

/*
 * Now get the boot sector of the ramdisk and check that its correct. If
 * we are simulating a hard disk the boot sector contains the partition
 * table, which we have to analyze. Then load the partitions boot sector.
 */

4:	push	ds
	mov	ax,ds
	mov	es,ax
	mov	ax,cs
	mov	ds,ax
	mov	bx,offset tmpbuf
	xor	al,al			# indicate read
	xor	edx,edx			# first sector
	call	rwsect			# read boot sector
	pop	ds
	mov	si,offset rderr
	jc	doerr
	cmp	byte ptr cs:[drvid],DISKID_HD
	jb	2f			# there is no partition table for
					# floppy disks
	cld
	mov	si,offset tmpbuf
	mov	di,offset partbuf	# save partition table for later
	mov	cx,SECTSIZE / 2
	rep	movsw
	mov	si,offset dskerr
	cmp	word ptr PART_SIG_OFS[partbuf],PART_SIG
	jne	doerr
	lea	di,PART_TABLE_OFS[partbuf]
	cmp	byte ptr part_status[di],PART_ACTIVE
	jne	doerr
	cmp	byte ptr part_type[di],PART_TYPE_16_SMALL
	je	1f
	cmp	byte ptr part_type[di],PART_TYPE_16_LARGE
	je	1f
	cmp	byte ptr part_type[di],PART_TYPE_12
	jne	doerr
1:	mov	edx,part_first_abs_sect[di]
	push	ds
	mov	ax,cs
	mov	ds,ax
	xor	al,al
	call	rwsect			# read boot sector
	pop	ds
	mov	si,offset rderr
	jc	doerr

2:	mov	si,offset dskerr
	cmp	word ptr BOOT_SIG_OFS[bx],BOOT_SIG
	jne	doerr
	mov	al,cs:[drvid]
	cmp	byte ptr bootrec_boot_id[bx],al
	jne	doerr			# check boot disk number
	cmp	word ptr bootrec_bytes_per_sect[bx],SECTSIZE
	jne	doerr			# check sector size
	mov	ax,cs:[hdnum]
	cmp	word ptr bootrec_head_num[bx],ax
	jne	doerr			# check number of heads
	mov	ax,cs:[secptk]
	cmp	word ptr bootrec_sect_per_track[bx],ax
	jne	doerr			# check sectors per track

/*
 * Everything is right, so we can now move the resident section to the
 * end of the conventional memory. With some older bootroms there is
 * no chance of returning from this point on. Only newer PXE compatible
 * bootroms set the top of memory correctly.
 * Note that the resident section doesnt start at offset 0, so we have
 * to set the segment address to somewhere lower.
 * Also note that the BOOTP record immediately follows the resident
 * section, so we have to copy it out of our data area as well.
 */

.ifgt ASM_DEBUG
	mov	si,offset resmsg
	call	prnstr
	call	prcrlf
.endif
	call	getmem			# get memory size in kB
	cld	
	shl	ax,6			# compute last usable segment
	mov	bx,offset btpnew
	mov	cx,bx
	mov	dx,[bootplen]		# save length of BOOTP record into
	mov	cs:[btplen],dx		# resident area
	add	bx,dx
	shr	bx,4			# compute size of code segment in
	inc	bx			# paragraphs
	sub	ax,bx			# compute new code segment
	mov	es,ax			# set source and destination ptrs
	push	ds
	mov	ax,cs
	mov	ds,ax
	mov	si,offset start_resident
	mov	di,si
	sub	cx,si			# compute size of resident area
	rep	movsb			# move it
	pop	ds
	mov	si,offset bootpbuf
	mov	cx,dx
	rep	movsb			# copy the BOOTP record

/*
 * Setup all interrupt vectors. Remember to set interrupt 15h only if
 * required to protect the ramdisk.
 */

	cli
	xor	ax,ax
	mov	gs,ax			# save old interrupt vectors in
	mov	eax,gs:[I13_INT]	# copy of resident area
	mov	es:[old13h],eax
	mov	eax,gs:[I15_INT]
	mov	es:[old15h],eax
	mov	eax,gs:[I2F_INT]
	mov	es:[old2Fh],eax
	mov	eax,gs:[IF1_INT]
	mov	es:[oldF1h],eax
	mov	eax,gs:[IF8_INT]
	mov	es:[oldF8h + 0],eax

	mov	bx,es
	test	byte ptr cs:[ldflags],BOOT_FLAG_INT15
	jz	1f
	mov	word ptr gs:[I15_INT + 2],bx
	mov	word ptr gs:[I15_INT + 0],offset int15
1:	mov	word ptr gs:[I13_INT + 2],bx
	mov	word ptr gs:[I13_INT + 0],offset int13
	mov	word ptr gs:[I2F_INT + 2],bx
	mov	word ptr gs:[I2F_INT + 0],offset int2F
	mov	word ptr gs:[IF8_INT + 2],bx
	mov	word ptr gs:[IF8_INT + 0],offset intF8
	mov	eax,es:[if1sig]		# copy installation check string
	mov	gs:[IF1_INT],eax	# into interrupt vector F1h
	sti

/*
 * With older DOS versions or FreeDOS it is not possible to use function
 * 0x4A06 in order to reserve the memory for the resident area. With these
 * DOS versions we have to decrease the amount of conventional memory so
 * that DOS does not overwrite this area. Lateron, it is no longer possible
 * to remove the resident area when removing the ramdisk. It will remain
 * as lost memory.
 * Note that we should not overwrite ES here! It has to remain 0.
 */

	test	byte ptr cs:[ldflags],BOOT_FLAG_NORPL
	jz	2f
	mov	ax,offset start_resident
	shr	ax,4
	add	ax,bx
	shr	ax,6
	mov	gs:[BIOS_SEG * 16 + BIOS_BASEMEM],ax
2:

/*
 * Output some debugging messages by simply calling the interrupt vectors
 * which we just created.
 */

.ifgt DRV_DEBUG
	mov	ax,0x4A06
	int	INT_MULTI
	mov	si,offset cnvmsg
	call	prnstr			# print amount of conventional
	movzx32	eax,dx			# memory
	call	prndwd
	call	prcrlf

	mov	si,offset extmsg
	call	prnstr			# print amount of extended memory
	mov	eax,cs:[extsiz]
	call	prndwd
	call	prcrlf
.endif

/*
 * The boot sector had to be placed into a temporary buffer to avoid
 * overwriting any bootrom structures. However, a call back to the
 * bootrom is no longer possible, so we can move the bootblock where
 * it belongs. Then call the boot sector with all required parameters.
 * Note that ES is still 0 at this point.
 */

	cld
	xor	ax,ax
	mov	es,ax
	mov	si,offset tmpbuf
	mov	di,BOOT_OFFSET
	mov	cx,SECTSIZE / 2
	rep	movsw

.ifgt ASM_DEBUG
	mov	si,offset dbgmsg
	call	prnstr
	call	prcrlf
.endif
.ifgt ASM_FREEZE_AFTER_INIT
1:	jmp	1b
.else
.ifgt ASM_KEYPRESS_AFTER_INIT
	mov	si,offset keymsg
	call	prnstr
	xor	ah,ah
	int	INT_KBD
	call	prcrlf
.endif
	mov	dl,cs:[drvid]
	lea	si,part_status[partbuf]
	xor	ax,ax
	mov	ds,ax
	ljmp	0,BOOT_OFFSET
.endif



/*
 *====================================================================
 *
 * Decompress the ramdisk image. This routine expects the following
 * global variables to be set correctly:
 *
 *	ds:[imgsize]	- size of compressed ramdisk image in kB
 *	ds:[hdrseg]	- segment of image header buffer
 *	ds:[bufseg]	- segment of image data buffer
 *	cs:[rdaddr]	- ramdisk address
 *	cs:[rdsize]	- total size of ramdisk in kB
 *
 * Input:  none
 * Output: carry flag set if error
 * Registers changed: EAX, EBX, ECX, EDX, SI, DI
 */
decomp:	push	es
	push	fs

/*
 * First move the header of the compressed ramdisk image into the
 * appropriate buffer. This header can never exceed 64kB in size, so
 * we simply move the first 64kB into the buffer. This saves us a lot
 * of code, and it doesnt hurt even if the header is much smaller.
 */

	movzx32	ebx,word ptr [hdrseg]
	mov	es,bx			# compute linear address of header
	shl	ebx,4			# buffer
	mov	ecx,[imgsize]		# get size of ramdisk image
	cmp	ecx,64
	jbe	1f			# if the ramdisk image is larger than
	mov	ecx,64			# 64kB, then only get the first 64kB
1:	shl	ecx,10
	mov	eax,cs:[rdaddr]
	call	movmem			# move image header into low memory
	jc	8f

/*
 * Check that the ramdisk image starts with the correct signature and that
 * the header checksum is correct. Also check that the values in the header
 * are correct.
 */

	mov	si,offset hdrsig
	xor	di,di
	mov	cx,RDSIGLEN		# check for correct signature
	repe	cmpsb
	jne	8f

	mov	cx,es:[rdheader_hdrsize]
	test	cx,0x0001		# the header has to have an even size
	jnz	8f
	shr	cx,1			# get number of words in header
	xor	bx,bx
	xor	ax,ax
2:	add	ax,es:[bx]		# compute checksum
	add	bx,2
	loop	2b
	or	ax,ax			# checksum has to be zero
	jnz	8f

	mov	ax,es:[rdheader_blksize]
	cmp	ax,RDBLKSIZE		# check block size
	ja	8f
	test	ax,0x1FFF		# has to be a multiple of 8kB
	jnz	8f
	mov	cx,es:[rdheader_blknum]
	cmp	cx,RDMAXBLKNUM		# check number of blocks
	ja	8f

/*
 * Compute the source and destination address of the last image block. Since
 * we decompress the image in-position, we have to do the decompression from
 * top to bottom.
 */

	dec	cx			# compute number of full blocks
	mov	bx,_I(rdheader_blklist)	# get pointer to list of block sizes
	xor	edi,edi
	movzx32	esi,word ptr es:[rdheader_hdrsize]
	movzx32	eax,word ptr es:[rdheader_blksize]
3:	movzx32	edx,word ptr es:[bx]	# get compressed size of next block
	add	bx,2
	add	esi,edx			# compute offset of compressed block
	add	edi,eax			# compute offset of decompressed block
	loop	3b

	movzx32	eax,word ptr es:[bx]	# get compressed size of last block
	add	eax,esi			# compute total compressed image size
	add	eax,0x000003FF
	shr	eax,10			# compute number of kB
	cmp	eax,[imgsize]		# check if compressed image size is correct
	jne	8f

	movzx32	eax,word ptr es:[rdheader_lastsize]
	cmp	ax,es:[rdheader_blksize]
	ja	8f			# last block should not be too large
	add	eax,edi			# compute size of decompressed image
	add	eax,0x000003FF
	shr	eax,10
	cmp	eax,cs:[rdsize]		# check that image does not exceed
	ja	8f			# total ramdisk size
	mov	[imgsize],eax

	add	esi,cs:[rdaddr]		# compute ramdisk addresses
	add	edi,cs:[rdaddr]

/*
 * Now decompress each block. We first copy the block into the block buffer,
 * and then use the LZO1X algorithm to decompress it in-place. Then copy the
 * decompressed block back into the ramdisk memory. We do this decompression
 * from top to bottom.
 * At this point, ES:BX still points to the compressed size of the last block,
 * ESI contains the linear address of the last block in the compressed image,
 * and EDI contains the linear address where the decompressed last block should
 * get placed.
 */

	mov	dx,es:[rdheader_blknum]
4:	push	bx
	movzx32	ecx,word ptr es:[bx]	# get compressed size of block
	movzx32	ebx,word ptr [bufseg]	# get linear address of data buffer
	shl	ebx,4
	mov	eax,65534
	sub	eax,ecx
	add	ebx,eax			# put compressed data at top of buffer
	mov	eax,esi			# get linear address of compressed block
	call	movmem			# moved compressed data into buffer
	mov	eax,ebx
	pop	bx			# CX remains unchanged
	jc	8f

	push	bx
	mov	bx,es:[rdheader_blksize]
	cmp	dx,es:[rdheader_blknum]
	jne	2f
	mov	bx,es:[rdheader_lastsize]
2:	cmp	cx,bx			# check if the block is compressed
	je	7f			# at all, and copy it verbatim if not
	pop	bx

	push	es
	push	dx
	push	esi
	push	edi
	mov	esi,eax
	mov	dx,es:[rdheader_blksize]
	movzx32	eax,word ptr [bufseg]	# put max. decompression size into DX
	mov	fs,ax			# put source address into FS:SI
	mov	es,ax			# put destination address into ES:DI
	shl	eax,4			# CX already contains the length of the
	sub	esi,eax			# compressed data
	xor	di,di
	call	lzo1x_decompress	# decompress memory block
	pop	edi
	pop	esi
	pop	dx
	pop	es
	jc	8f			# check for error

	movzx32	ecx,ax
	mov	ax,es:[rdheader_blksize]
	cmp	dx,es:[rdheader_blknum]
	jne	5f
	mov	ax,es:[rdheader_lastsize]
5:	cmp	cx,ax			# check that the decompressed block
	jne	8f			# size is correct

	push	bx
	movzx32	eax,word ptr [bufseg]
	shl	eax,4
7:	mov	ebx,edi
	call	movmem			# copy decompressed data back into
	pop	bx			# the ramdisk
	jc	8f

	dec	dx			# check if first block processed
	jz	6f
	sub	bx,2			# advance to previous block
	movzx32	eax,word ptr es:[rdheader_blksize]
	sub	edi,eax
	movzx32	eax,word ptr es:[bx]
	sub	esi,eax
	jmp	4b

/*
 * Return to caller
 */

6:	cmp	edi,cs:[rdaddr]		# check for correct ramdisk address
	je	9f
8:	stc				# return with error
9:	pop	fs
	pop	es
	ret




/*
 *====================================================================
 *
 * String and constants definitions
 */
	.data


/* Startup signature */
sigmsg:	.byte	0x0D, 0x0A
	.ascii	"DOS Net Boot Image Loader "
	.ascii	VERSION
	.byte	0x0D, 0x0A
	.ascii	COPYRIGHT
	.byte	0x0D, 0x0A
	.byte	0x0D, 0x0A
	.byte	0


/* Boot image header vendor magic ID */
vmagic:	.asciz	VENDOR_MAGIC


/* Ramdisk drive type table: total number of sectors */
typtab:	.word	720		# 0x01
	.word	2400		# 0x02
	.word	1440		# 0x03
	.word	2880		# 0x04
	.word	-1		# 0x05
	.word	5760		# 0x06
	.word	0


/* Compaq signature */
compaq:	.ascii	"COMPAQ"


/* Signature of compressed ramdisk image header */
hdrsig:	.asciz	RDHDRSIG


/* Decompression messages */
decmsg:	.asciz	"Decompressing ramdisk... "
donmsg:	.asciz	"done"

/* Error messages */
recerr:	.asciz	"Error in load record data"
rderr:	.asciz	"Error while accessing ramdisk"
geoerr:	.asciz	"Invalid ramdisk geometry"
dskerr:	.asciz	"Wrong ramdisk image"
decerr:	.asciz	"Decompression error"
memerr:	.asciz	"Not enough memory for decompression"

.ifgt ASM_DEBUG
/* Some debugging messages */
rdmsg:	.asciz	"Ramdisk address = "
resmsg:	.asciz	"Making driver resident"
.ifgt ASM_KEYPRESS_AFTER_INIT
keymsg:	.asciz	"Press any key to continue"
.endif
.ifgt ASM_FREEZE_AFTER_INIT
dbgmsg:	.asciz  "Freezing!"
.else
dbgmsg:	.asciz	"Calling boot block"
.endif
.endif

.ifgt DRV_DEBUG
cnvmsg:	.asciz	"RAMDISK: reporting conventional memory size: "
extmsg:	.asciz	"RAMDISK: reporting extended memory size: "
.endif




/*
 *====================================================================
 *
 * Variable definitions
 */
	.bss

	.extern	bootplen		# space to hold BOOTP record
	.extern	bootpbuf		# length of BOOTP record
	.extern	stktop			# top of stack pointer

	.lcomm	tmpbuf,SECTSIZE		# temporary buffer for boot sector
	.lcomm	partbuf,SECTSIZE	# partition table buffer
	.lcomm	imgsize,4		# ramdisk image size in kB
	.lcomm	hdrseg,2		# compressed header segment
	.lcomm	bufseg,2		# decompression buffer segment
	.lcomm	freeseg,2		# first free segment following loader




/*
 *====================================================================
 *
 * Start of resident section. This will be placed at the end of the
 * conventional RAM area.
 *
 * In order to be able to copy this in one piece with all offsets
 * set correctly by the assembler, we have to put the data values
 * of the resident section into the text section. Also, to seperate
 * this area from all the other text sections, we use subsection
 * number 1 for the resident area.
 *
 *====================================================================
 */
	.text 1

	.align	16			# has to be paragraph aligned

start_resident:				# indicate start of resident section



/*
 *====================================================================
 *
 * New interrupt 2Fh routine. This routine gets called by IO.SYS
 * in order to determine the maximum amount of memory usable to
 * DOS. This only works with DOS versions 5.0 and higher. The DOS
 * function which gets installed into the interrupt 2Fh vector
 * does not call the old vector (i.e. it does not daisy-chain),
 * and therefore we can redirect 2Fh without a problem even when
 * considering to remove the ram disk lateron.
 *
 * NOTE THAT THIS INTERRUPT HAS TO BE THE FIRST ROUTINE IN THE
 * RESIDENT SECTION!
 *
 * Input:  AX     -  Magic ID
 *         DX     -  segment following last usable byte
 * Output: DX     -  new segment  following last usable byte
 * Registers changed: DX
 */
int2F:	jmp	1f			# this has to be a relative jump
	nop

	.ascii	"RPL"			# magic ID string for DOS

1:	cmp	ax,0x4A06		# check for magic ID
	jne	9f
	mov	dx,offset start_resident
	shr	dx,4			# determine last usable segment
	push	ax			# from segment and offset of
	push	cs			# the resident section
	pop	ax
	add	dx,ax			# add offset to segment
	dec	dx
	pop	ax
	iret

9:	ljmp	cs:[old2Fh]		# jump to old interrupt routine



/*
 *====================================================================
 *
 * New interrupt 15h routine. This routine gets only installed if
 * we dont want or cant use XMS to protect the ramdisk. It catches
 * the "get extended memory" calls and returns a size reduced to the
 * start of the ramdisk.
 * Input:  AH  -  Function code
 * Output: depends on function
 * Registers changed: depends on function
 */
int15:	cmp	ah,0x88
	jne	1f
	mov	ax,15 * 1024		# return a maximum of 15 MB
	cmp	dword ptr cs:[extsiz],15 * 1024
	jae	7f
	mov	ax,cs:[extsiz + 0]	# return size of extended memory
	jmp	7f

1:	cmp	ax,0xE801
	jne	3f
	mov	ax,cs:[extsiz + 0]
	xor	bx,bx			# check if less than 15 MB
	cmp	dword ptr cs:[extsiz],15 * 1024
	jbe	2f
	mov	bx,cs:[extsiz + 0]
	mov	ax,cs:[extsiz + 2]
	sub	bx,15 * 1024		# compute amount of memory above 16 MB
	sbb	ax,0
	shrd	bx,ax,6			# compute number of 64 kB blocks
	mov	ax,15 * 1024		# set amount of memory below 16 MB
2:	mov	cx,ax			# prepare return registers
	mov	dx,bx
	jmp	7f

3:	cmp	ax,0xE820
	jne	4f
	stc
	mov	ah,0x86			# function not supported
	jmp	8f

4:	cmp	ax,0xE881
	jne	9f
	mov	eax,cs:[extsiz]		# get size of extended memory
	xor	ebx,ebx
	cmp	eax,15 * 1024		# above 16 MB?
	jbe	5f
	mov	ebx,15 * 1024		# determine amount of memory above
	sub	eax,ebx			# 16 MB in 64 kB chunks
	shr	eax,6
	xchg	eax,ebx
5:	mov	ecx,eax			# prepare return registers
	mov	edx,ebx

7:	clc
8:	push	ax
	pushf
	pop	ax
	push	bp
	mov	bp,sp
	mov	[bp + 8],al		# put the flags onto the stack
	pop	bp
	pop	ax
	iret

9:	ljmp	cs:[old15h]		# jump to old interrupt routine



/*
 *====================================================================
 *
 * New interrupt F8h routine. It can be used to retrieve several
 * values from the resident driver. This is version 2 of the
 * interface specification.
 * Input:  AX  -  function code
 * Output: depends on function:
 *
 * Installation check (AX = 0x9C00)
 *         AX     -  contains 0x009D
 *         BL     -  major version of this resident driver
 *         BH     -  minor version of this resident driver
 *
 * Return ramdisk size and address (AX = 0x9C01)
 *         EDX    -  address of ramdisk
 *         ECX    -  size of ramdisk in kb
 *
 * Return size and address of BOOTP block (AX = 0x9C02)
 *         BX:DX  -  address of BOOTP block
 *         CX     -  size of BOOTP block
 *
 * Return miscellaneous values for handling the ramdisk (AX = 0x9C03)
 *         AX     -  XMS handle for ram disk
 *         BX:DX  -  address of old interrupt vector table
 *         CL     -  ramdisk id
 *         CH     -  low byte of old system configuration word
 *
 * Remove ramdisk (AX = 0x9C04)
 *         AL     -  non-zero if error:
 *			1 = unable to remove ramdisk driver, ramdisk
 *			    remains active
 *			2 = unable to remove ramdisk driver, ramdisk
 *			    becomes unusable
 *
 * Return address of disk parameter block (AX = 0x9C05)
 *         BX:DX  -  address of new disk parameter block
 *
 * Registers changed: depends on function
 */
intF8:	cmp	ah,0x9C			# check for magic ID
	jne	9f
	cmp	al,0x01			# check for function number
	jne	1f
	mov	edx,cs:[rdaddr]		# return ramdisk address
	mov	ecx,cs:[rdsize]		# return ramdisk size
	jmp	9f

1:	cmp	al,0x02
	jne	2f
	mov	bx,cs			# return address of BOOTP record
	mov	dx,offset btpnew
	mov	cx,cs:[btplen]		# return BOOTP length
	jmp	9f

2:	cmp	al,0x03
	jne	3f
	mov	bx,cs			# return address of old interrupt
	mov	dx,offset oldints	# vector table
	mov	ax,word ptr cs:[xmshdl]	# return XMS handle
	mov	cl,byte ptr cs:[drvid]	# return drive id
	mov	ch,byte ptr cs:[syscnf]	# return system configuration
	jmp	9f

3:	cmp	al,0x04
	jne	4f
	call	rmrd			# remove ramdisk
	jmp	9f

4:	cmp	al,0x05
	jne	8f
	mov	bx,cs			# return address of new DPB
	mov	dx,offset new_dpb
	jmp	9f

8:	or	al,al
	jnz	9f
	xchg	al,ah			# return installation check code
	inc	ax
	mov	bx,RES_VERSION		# return interface version number
9:	iret



/*
 *====================================================================
 *
 * New interrupt 13h routine to handle disk accesses. It is different
 * for simulating either a floppy drive or a hard disk. DOS provides
 * a way for restoring this interrupt to its original value when we
 * want to remove the ramdisk lateron.
 * Note that we have to be careful when using 32-bit registers so that
 * the high word of a register doesnt get clobbered without being saved
 * first.
 * Input:  AH  -  function code
 *         DL  -  driver number
 * Output: carry flag set if error
 * Registers changed: depends on function
 */
int13:	sti				# we dont need interrupts disabled
	test	byte ptr cs:[disabled],0xFF
	jnz	1f			# check if ramdisk has been disabled

	push	ax			# check if XMS already initialized
	test	byte ptr cs:[ldflags],BOOT_FLAG_INT15
	jnz	5f
	mov	ax,cs:[xmsadr + 0]
	or	ax,cs:[xmsadr + 2]
	jnz	5f
	mov	ax,0x4300		# check if XMS available
	int	INT_XMS
	cmp	al,0x80			# XMS not available
	jne	5f
	push	bx
	push	es
	mov	ax,0x4310		# get XMS driver address
	int	INT_XMS
	mov	cs:[xmsadr + 0],bx	# save driver address
	mov	cs:[xmsadr + 2],es
	pop	es
	call	inixms			# initialize XMS
	pop	bx
5:	pop	ax


/*
 * Check that this call is for us, and wether we are to simulate a hard
 * disk or a floppy drive.
 */

	cmp	dl,byte ptr cs:[drvid]
	je	2f
	cmp	byte ptr cs:[drvid],DISKID_HD
	jae	int13h


/*
 *********************************************
 *                                           *
 * First comes the floppy drive redirector   *
 *                                           *
 *********************************************
 */

	cmp	dl,DISKID_HD		# check if its for a hard disk
	jb	3f
	test	byte ptr cs:[nohd],0xFF	# check if hard disk accesses allowed
	jz	1f
	cmp	ah,0x08			# function 0x08 should not return error
	mov	ah,DISKID_HD		# return with error
	jne	5f
	xor	dl,dl			# indicate no hard disk present
	jmp	0f			# return without error

/*
 * Handle function 0x08 for disk drives other than the ramdisk.
 */

3:	cmp	ah,0x08
	jne	1f
	pushf				# function 0x08 has to return the
	lcall	cs:[old13h]		# correct number of disk drives
	mov	dl,cs:[drvnum]
0:	xor	ah,ah			# never return an error
	jmp	6f

/*
 * Jump directly to the BIOS
 */

1:	ljmp	cs:[old13h]		# call the old interrupt routine

/*
 * Now handle all ramdisk functions. First check if the function number
 * is correct.
 */

2:	cmp	ah,0x18
	jbe	4f
	mov	ah,0x01			# unknown command
5:	stc
6:	push	ds
	jmp	f13end

/*
 * Determine the handlers address according to the function number in AH
 * and jump to it.
 */

4:	push	ds
	push	cs
	pop	ds			# set data segment
	push	bx
	movzx	bx,ah
	shl	bx,1			# compute pointer into routine table
	jmp	fntab[bx]


/*
 *********************************************
 *                                           *
 * Now comes the hard disk drive redirector  *
 *                                           *
 *********************************************
 */

int13h:	cmp	dl,DISKID_HD		# check if its for a floppy drive
	jb	1b			# then directly call the BIOS

/*
 * Handle function 0x08 for hard disk drives other than the ramdisk.
 */

	cmp	ah,0x08
	jne	7f
	dec	dl
	pushf				# function $08 has to return the
	lcall	cs:[old13h]		# correct number of disk drives
	mov	dl,cs:[drvnum]
	jmp	0b			# always return without error

/*
 * Handle function 0x15 for disk drives other than the ramdisk. This is
 * the only function besides 0x08 which returns a value in DX and therefore
 * has to have special handling.
 */

7:	push	dx
	dec	dl
	cmp	ah,0x15
	jne	8f
	pushf
	lcall	cs:[old13h]		# call the BIOS for handling
	jc	9f
	cmp	ah,0x03			# DX is only used if AH = $03
	jne	9f
	add	sp,0x0002		# remove DX from stack if the BIOS
	jmp	6b			# returned a value in it

/*
 * Handle all other functions for drives other than the ramdisk. This will
 * just call the original BIOS handler.
 */

8:	pushf
	lcall	cs:[old13h]		# simply call the old int 13h routine
9:	pop	dx
	jmp	6b


/*
 * Save the return status into the BIOS data area and return to the caller
 * while preserving the carry flag.
 */

f13end:	push	es			# return from function handler
	push	bx			# this code is not allowed to change
	call	getsts			# any register or flag
	mov	es:[bx],ah		# set disk operation status
	pop	bx
	pop	es
intend:	pop	ds
	push	ax
	pushf
	pop	ax
	push	bp
	mov	bp,sp
	mov	byte ptr [bp + 8],al	# put the flags onto the stack
	pop	bp
	pop	ax
	iret


/*
 * Function table
 */

fntab:	.word	f1300			# function 00: reset disk system
	.word	f1301			# function 01: return last error
	.word	f1302			# function 02: read disk
	.word	f1303			# function 03: write disk
	.word	f1304			# function 04: verify disk
	.word	f1305			# function 05: format disk
	.word	f1306			# function 06: format track
	.word	f1307			# function 07: format disk
	.word	f1308			# function 08: get drive parameters
	.word	f1309			# function 09: intialize controller
	.word	f130A			# function 0A: read long sectors
	.word	f130B			# function 0B: write long sectors
	.word	f130C			# function 0C: seek for cylinder
	.word	f130D			# function 0D: disk reset
	.word	f130E			# function 0E: read sector buffer
	.word	f130F			# function 0F: write sector buffer
	.word	f1310			# function 10: check if drive ready
	.word	f1311			# function 11: recalibrate drive
	.word	f1312			# function 12: controller ram diagnostic
	.word	f1313			# function 13: drive diagnostic
	.word	f1314			# function 14: controller int diagnostic
	.word	f1315			# function 15: get disk type
	.word	f1316			# function 16: detect disk change
	.word	f1317			# function 17: set media type for format
	.word	f1318			# function 18: set media type for format



/*
 *====================================================================
 *
 * Function 00 - reset disk system
 */
f1300:	test	word ptr [syscnf],0x0001
	jz	1f			# check if we have physical floppy
	push	dx			# drives at all
	xor	dl,dl			# always reset the floppy system
	pushf
	lcall	[old13h]		# call old disk interrupt
	pop	dx
	jc	2f
1:	xor	ah,ah			# no error
2:	pop	bx
	jmp	f13end



/*
 *====================================================================
 *
 * Function 01 - return last error status
 *
 */
f1301:	push	es
	call	getsts			# get offset to status byte
	mov	ah,es:[bx]		# get disk operation status
	pop	es
	pop	bx
	clc
	jmp	intend



/*
 *====================================================================
 *
 * Function 02/03 - read/write from disk
 */
f1302:
f1303:	pop	bx			# get old BX from stack
	push	edx
	push	ax
	call	cvtsec			# get linear sector number
	pop	ax
	jnc	1f
	mov	ah,0x04			# error: sector not found
8:	pop	edx
	stc
	jmp	f13end			# terminate

1:	push	cx
	push	bx
	push	ax
	movzx	cx,al			# move number of sectors into CX
2:	jcxz	6f
	cmp	edx,[secnum]		# check if sector is still correct
	jb	3f
	pop	ax
	mov	ah,0x04			# error: sector not found
7:	sub	al,cl			# compute number of sectors processed
	pop	bx
	pop	cx
	jmp	8b

3:	pop	ax
	push	ax
	xor	al,al
	cmp	ah,0x02			# check if read or write sector
	je	4f
	inc	al
4:	call	rwsect			# actually handle request
	jnc	5f
	pop	ax
	mov	ah,0x20			# error: disk controller error
	jmp	7b

5:	inc	edx
	dec	cx			# proceed with next sector
	add	bx,SECTSIZE
	jmp	2b

6:	pop	ax
	pop	bx
	pop	cx
	pop	edx
	xor	ah,ah			# no error
	jmp	f13end



/*
 *====================================================================
 *
 * Function 08  -  get disk drive parameters
 */
f1308:	pop	bx			# get old BX from stack
	mov	dx,[hdnum]		# get number of heads
	dec	dx
	mov	dh,dl
	mov	dl,byte ptr [drvnum]	# get number of disk drives
	mov	cx,[cylnum]
	dec	cx
	xchg	cl,ch			# put low order 8 bits into CH
	shl	cl,6			# put high order 2 bits into CL
	mov	ax,[secptk]		# get number of sectors per track
	or	cl,al			# put it into CL
	mov	bl,[drvtyp]		# ramdisk drive type
	xor	ax,ax
	jmp	f13end



/*
 *====================================================================
 *
 * Function 04, 05, 06, 07, 09, 0D, 10, 11, 12, 13, 14, 16  -  no operation
 */
f1304:
f1305:
f1306:
f1307:
f1309:
f130D:
f1310:
f1311:
f1312:
f1313:
f1314:
f1316:	pop	bx			# get old BX from stack
	xor	ah,ah			# no error
	jmp	f13end



/*
 *====================================================================
 *
 * Function 0A, 0B, 0E, 0F, 17, 18  -  not implemented
 */
f130A:
f130B:
f130E:
f130F:
f1317:
f1318:	pop	bx			# get old BX from stack
	mov	ah,0x01			# invalid opcode
	stc
	jmp	f13end



/*
 *====================================================================
 *
 * Function 0C  -  seek for cylinder
 */
f130C:	pop	bx			# get old BX from stack
	push	edx
	push	ax
	call	cvtsec			# get linear sector number
	pop	ax
	mov	ah,0x00			# no error
	jnc	1f
	mov	ah,0x04			# error: sector not found
1:	pop	edx
	jmp	f13end			# terminate



/*
 *====================================================================
 *
 * Function 15  -  get disk type
 */
f1315:	pop	bx			# get old BX from stack
	mov	ah,0x02			# indicate floppy disk
	cmp	dl,DISKID_HD		# check if floppy disk type requested
	jb	9f
	inc	ah			# indicate hard disk
	mov	dx,word ptr [secnum + 0]
	mov	cx,word ptr [secnum + 2]
9:	clc
	jmp	f13end



/*
 *====================================================================
 *
 * Convert Cyl/Sec/Head notation into a linear sector number
 * Input:  CH  -  cylinder number
 *         CL  -  sector number, high bits of cylinder number
 *         DH  -  head number
 * Output: EDX -  linear sector number
 *         carry flag set if invalid sector number
 * Registers changed: AX,EDX
 */
cvtsec:	push	bx
	movzx	bx,dh			# move current head number into BX
	cmp	bx,[hdnum]		# check if head number is correct
	jae	8f			# maximum number of heads is always 2

	mov	al,ch			# compute track number into AX, the
	mov	ah,cl			# upper two bits of CL are the high
	shr	ah,6			# bits of the 10 bit cylinder number
	cmp	ax,[cylnum]
	jae	8f
	mul	word ptr [hdnum]	# compute track number from cylinders
	add	ax,bx
	adc	dx,0

	mov	bx,ax
	mov	ax,dx
	mul	word ptr [secptk]	# compute number of first sector on
	xchg	bx,ax			# track
	mul	word ptr [secptk]
	add	dx,bx

	mov	bl,cl
	and	bx,0x003F		# move sector number into AX
	cmp	bx,[secptk]		# check if sector number is correct
	ja	8f
	dec	bx			# sector numbers start with 1
	js	8f			# therefore sector 0 does not exist
	add	ax,bx			# compute final sector number
	adc	dx,0
	shl	edx,16			# convert into 32-bit value
	mov	dx,ax
	cmp	edx,[secnum]		# check if the sector is valid
	jae	8f
	clc				# no error
	jmp	9f
8:	stc				# return with error
9:	pop	bx
	ret



/*
 *====================================================================
 *
 * Read/write a sector from the ram disk. This routine requires a
 * sector to be 512 bytes long.
 * Input:  AL     -  non-zero if write to ram disk
 *         EDX    -  logical sector number
 *         ES:BX  -  pointer to destination buffer
 * Output: carry flag set if error
 * Registers changed: AX
 */
rwsect:	push	cx
	push	dx
	mov	ch,al			# save direction indicator
	mov	dx,es
	mov	ax,dx
	shr	ax,12
	shl	dx,4			# compute linear buffer address
	add	dx,bx
	adc	ax,0
	or	ch,ch			# check direction of transfer
	jz	1f			# set source address for write
	mov	word ptr [rd_srcb + 0],dx
	mov	byte ptr [rd_srcb + 2],al
	mov	byte ptr [rd_srcc],0
	jmp	2f			# set destination address for read
1:	mov	word ptr [rd_dstb + 0],dx
	mov	byte ptr [rd_dstb + 2],al
	mov	byte ptr [rd_dstc],0
2:	pop	dx

	push	edx
	shl	edx,9			# compute linear ramdisk address
	add	edx,[rdaddr]		# from sector number
	or	ch,ch			# check direction of transfer
	jz	3f			# set destination address for write
	mov	word ptr [rd_dstb + 0],dx
	shr	edx,16
	mov	byte ptr [rd_dstb + 2],dl
	mov	byte ptr [rd_dstc],dh
	jmp	4f			# set source address for read
3:	mov	word ptr [rd_srcb + 0],dx
	shr	edx,16
	mov	byte ptr [rd_srcb + 2],dl
	mov	byte ptr [rd_srcc],dh
4:	push	es
	push	si
	mov	ax,cs
	mov	es,ax
	mov	si,offset rd_gdt
	mov	cx,SECTSIZE / 2		# copy 512 bytes, e.g. 256 words
	mov	ax,0x8700		# let the BIOS move the sector
	int	INT_MISC
	pop	si
	pop	es
	pop	edx
	pop	cx
	ret



/*
 *====================================================================
 *
 * Return a pointer to the disk drive status byte. This routine should
 * not change any flags!
 * Input:  none
 * Output: ES:BX  -  pointer to disk drive status byte
 * Registers changed: BX, ES
 */
getsts:	mov	bx,BIOS_SEG		# status byte is in BIOS data
	mov	es,bx			# segment
	mov	bx,cs:[statof]
	ret



/*
 *====================================================================
 *
 * Initialize the XMS interface. This is necessary to prevent the
 * ram disk from getting overwritten. The way this works is to
 * first allocate all of the available XMS, then resize the memory
 * block to end just above the ramdisk and lock it. Unfortunately
 * we have to do it this complicated because there is no way of
 * knowing how the XMS is going to allocate the available memory.
 * Another problem is that at least HIMEM.SYS requires up to 256
 * bytes of stack, and we cannot assume the caller of INT 13 to
 * provide that much so we have to change stacks. Note that we
 * have to provide two different versions of this routine depending
 * on the version of the XMS handler.
 * Input:  none
 * Output: none
 * Registers changed: AX, BX
 */
inixms:	call	setstk			# set new stack

/*
 * First check that the XMS version number is correct. To support all
 * necessary functions it has to be version 2.0+.
 */

	xor	ah,ah
	call	callxm			# get version number
	cmp	ah,0x02
	jb	8f			# wrong XMS version
	cmp	ah,0x03
	jb	1f			# call XMS 2.0 routine

/*
 * XMS version 3.0 can handle more than 64MB of memory
 * First determine the amount of total memory installed. Then grab all
 * memory which is available, and finally resize it to fit the ramdisk.
 */

	push	eax			# save upper 16 bits of EAX
	mov	ah,0x88
	xor	bl,bl			# get amount of extended memory
	call	callxm
	mov	edx,eax
	pop	eax
	or	bl,bl
	jz	2f			# this can happen with himem.sys used
					# by OpenDOS even if it says V3.0
	mov	edx,[rdsize]		# in this case just say that we have
	add	edx,HMA_SIZE		# enough for the ramdisk

2:	push	edx
	mov	ah,0x89
	call	callxm			# grab largest memory block
	pop	ecx
	or	ax,ax			# check for error
	jz	1f			# if error, try 16-bit routine
	mov	[xmshdl],dx		# save handle

	mov	ebx,[rdsize]		# get ramdisk size
	add	ebx,HMA_SIZE		# care for missing HMA
	cmp	ebx,ecx			# check if enough memory for ram disk
	jae	7f			# if not, leave all memory allocated

	mov	ah,0x8F			# handle is still in DX
	call	callxm			# resize memory block
	jmp	7f

/*
 * Now handle the XMS version 2.0 protocol. This protocol does not allow
 * more than 64MB of memory.
 */

1:	mov	ah,0x08
	xor	bl,bl			# get amount of extended memory
	call	callxm
	mov	dx,ax
	or	bl,bl			# check for error
	jnz	8f

	push	dx			# grab largest block - which is whole
	mov	ah,0x09			# memory because there should be no
	call	callxm			# other process using XMS
	pop	cx
	or	ax,ax			# check for error
	jz	8f
	mov	[xmshdl],dx		# save handle

	mov	ebx,[rdsize]		# get size of ramdisk
	add	ebx,HMA_SIZE		# care for a missing HMA
	cmp	ebx,0x00010000		# if ram disk is larger than 64 MB
	jae	7f			# we dont have to check memory amount
	cmp	bx,cx			# check if enough memory for ram disk
	jae	7f			# if not leave all memory allocated

	mov	ah,0x0F			# handle is still in DX
	call	callxm			# resize memory block

/*
 * Now lock the memory block and check that the physical address of the
 * memory block is correct.
 */

7:	mov	dx,[xmshdl]
	mov	ah,0x0C			# lock memory block
	call	callxm
	add	bx,0x03FF
	adc	dx,0x0001		# add 65kb - maximum difference
	sub	bx,word ptr [rdaddr + 0]
	sbb	dx,word ptr [rdaddr + 2]
	jnc	9f

/*
 * Thats it. Restore all registers and swap the stack back to its
 * original state.
 */

8:	mov	word ptr [xmshdl],0	# in case of error dont return handle
9:	call	rststk			# restore old stack
	ret



/*
 *====================================================================
 *
 * Call XMS driver.
 * Input:  AH  -  function code
 *         other registers depending on function code
 * Output: depends on called function
 * Registers changed: depends on called function
 */
callxm:	push	ax
	push	bp
	push	ax
	mov	bp,sp
	mov	ax,[bp + 6]
	mov	[bp + 4],ax		# make far return address from
	mov	[bp + 6],cs		# near call
	pop	ax
	pop	bp
	push	dword ptr cs:[xmsadr]
	lret				# call XMS driver



/*
 *====================================================================
 *
 * Set new stack. After calling this routine we can use EBX, ECX and EDX.
 * Input:  none
 * Output: none
 * Registers changed: AX, BX, DS, SS, SP
 */
setstk:	cli
	pop	bx			# get return address
	mov	ax,sp			# save old stack pointer
	mov	word ptr cs:[oldstk + 0],ax
	mov	word ptr cs:[oldstk + 2],ss
	mov	ax,cs
	mov	ss,ax
	mov	sp,offset newtos - 2	# change to new stack
	sti
	push	ebx			# we have to save the upper 16
	push	ecx			# bits of EBX too!
	push	edx
	push	si			# save all registers
	push	di
	push	es
	push	ds
	mov	ax,cs			# set DS to current segment
	mov	ds,ax
	jmp	bx			# return to caller



/*
 *====================================================================
 *
 * Reset stack to old stack
 * Input:  none
 * Output: none
 * Registers changed: all (reset to old state)
 */
rststk:	pop	ax			# get return address
	pop	ds
	pop	es
	pop	di
	pop	si			# restore all registers
	pop	edx
	pop	ecx
	pop	ebx
	mov	bx,ax
	cli
	mov	ax,word ptr cs:[oldstk + 0]
	mov	ss,word ptr cs:[oldstk + 2]
	mov	sp,ax
	sti
	jmp	bx			# return to caller



/*
 *====================================================================
 *
 * Remove ramdisk from memory. This involves restoring all interrupt
 * vectors and freeing all used memory. The DOS drive table has to
 * get restored by the caller in order to make the physical floppy
 * drive accessible again. Note that this is different from the
 * previous version of this routine.
 * Since we need to call the XMS driver, we have to switch stacks like
 * with inixms.
 * Input:  none
 * Output: AL  -  non-zero if error:
 *			1 = ramdisk remains active
 *			2 = ramdisk becomes unusable
 * Registers changed: EAX
 */
rmrd:	push	bx
	call	setstk			# set new stack

/*
 * First restore the interrupt vectors. Int 13h is special as it is
 * redirected by DOS. However, DOS provides a function to restore
 * that interrupt. Interrupt 2Fh doesnt have to get restored because
 * DOS does never call the old interrupt again. If we cant restore
 * interrupt 15h it doesnt matter. The ramdisk then just remains
 * physically in memory unless we used the XMS driver.
 */

	xor	ax,ax
	mov	es,ax			# first check that nobody redirected
	mov	ax,cs			# our own interrupts
	cmp	word ptr es:[IF8_INT + 2],ax
	jne	8f
	cmp	word ptr es:[IF8_INT + 0],offset intF8
	jne	8f
	mov	eax,es:[IF1_INT]	# interrupt F1h contains a signature
	cmp	eax,[if1sig]		# and no vector
	je	1f
8:	call	rststk			# return with error
	pop	bx
	mov	al,0x01
	ret

1:	push	ds
	les	bx,[old13h]		# get old interrupt vector 13h
	mov	dx,bx
	mov	ax,es			# save it into DS:DX and ES:BX
	mov	ds,ax
	mov	ah,0x13
	int	INT_MULTI		# call DOS to restore vector
	mov	ax,ds
	mov	cx,cs
	cmp	ax,cx			# check that its indeed our interrupt
	jne	4f			# which we are replacing
	cmp	dx,offset int13
	je	5f
4:	or	byte ptr [ldflags],BOOT_FLAG_KEEP
	inc	byte ptr cs:[disabled]
	les	bx,cs:[old13h]		# if unable to restore interrupt 13h,
	mov	ah,0x13			# set it back again and disable the
	int	INT_MULTI		# ramdisk
5:	pop	ds

	cli				# restore the other interrupts
	xor	ax,ax
	mov	es,ax
	mov	eax,[oldF1h]
	mov	es:[IF1_INT],eax
	mov	eax,[oldF8h]
	mov	es:[IF8_INT],eax

	test	byte ptr [ldflags],BOOT_FLAG_INT15
	jz	2f
	mov	ax,cs
	cmp	word ptr es:[I15_INT + 2],ax
	jne	1f
	cmp	word ptr es:[I15_INT + 0],offset int15
	jne	1f
	mov	eax,[old15h]
	mov	es:[I15_INT],eax
	jmp	2f

1:	or	byte ptr [ldflags],BOOT_FLAG_KEEP
2:	sti

/*
 * Restore the BIOS system configuration flag
 */

	cmp	byte ptr [drvid],DISKID_HD
	jae	4f			# only necessary with floppy drive
	mov	ax,BIOS_SEG
	mov	es,ax
	mov	ax,[syscnf]
	and	ax,0x00C1		# isolate the floppy relevant bits
	mov	bx,es:[BIOS_SYSCNF]
	and	bx,0xFF3E
	or	ax,bx			# set old floppy flags and restore
	mov	es:[BIOS_SYSCNF],ax	# old configuration

/*
 * Next remove the ramdisk image from extended memory using the XMS driver.
 * If we cant remove it, well thats just bad luck because it will remain
 * eating up memory, but nothing else.
 */

4:	mov	dx,[xmshdl]
	or	dx,dx			# only free memory if we really
	jz	7f			# assigned it with XMS
	push	dx
	mov	ah,0x0D
	call	callxm			# unlock memory block
	pop	dx
	or	ax,ax			# dont free block if error
	jz	7f
	push	dx
	mov	ax,0x0E
	call	callxm			# get lock count
	pop	dx
	or	ax,ax
	jz	7f
	or	bh,bh			# dont try to free the block if
	jnz	7f			# the lock count is not zero
	mov	ah,0x0A
	call	callxm			# free memory block

/*
 * Finally we can remove the memory used by the ramdisk driver. We only
 * reset the owner field of the memory control block to 0 to indicate
 * it as free. However, this is only possible if the DOS version
 * supports RPL loading. If the ramdisk has been disabled, then interrupt
 * 13h couldnt get restored, so dont remove this code.
 */

7:	test	byte ptr [ldflags],BOOT_FLAG_NORPL + BOOT_FLAG_KEEP
	jnz	3f
	cld
	mov	dx,offset start_resident
	shr	dx,4
	mov	ax,cs
	add	dx,ax
	sub	dx,2
	mov	es,dx
	mov	di,0x0001
	mov	al,es:[di - 1]		# get MCB type
	cmp	al,0x4D
	je	6f			# has to be either M or Z
	cmp	al,0x5A
	jne	3f
6:	xor	ax,ax			# set owner field to 0
	stosw
	add	di,5
	mov	cx,4			# clear owner name
	rep	stosw

9:	call	rststk			# restore old stack
	pop	bx
	xor	al,al			# return without error
	ret

3:	call	rststk
	pop	bx			# indicate that we removed the
	mov	al,0x02			# ramdisk but not the driver
	ret




/*
 *====================================================================
 *
 * Variables for the resident section. This has to be within the text
 * segment of the resident section.
 */
		.align	2

oldints:
old13h:		.long	0		# old interrupt 13h vector
old15h:		.long	0		# old interrupt 15h vector
old2Fh:		.long	0		# old interrupt 2Fh vector
oldF1h:		.long	0		# old interrupt F1h vector
oldF8h:		.long	0		# old interrupt F8h vector


/*
 * Disk parameters for ram disk
 */

syscnf:		.word	0		# system configuration from BIOS
statof:		.word	BIOS_FDSTAT	# offset to BIOS disk status byte
rdaddr:		.long	0		# base address of ram disk
rdsize:		.long	0		# total size of ram disk in kB
cylnum:		.word	80		# number of cylinders
secnum:		.long	2400		# number of sectors on disk
secptk:		.word	15		# number of sectors per track
hdnum:		.word	2		# number of heads
drvnum:		.byte	1		# number of disk drives
drvid:		.byte	0		# ramdisk drive id
drvtyp:		.byte	0		# ramdisk drive type
nohd:		.byte	0		# no-hard-disk flag
disabled:	.byte	0		# non-zero if ramdisk disabled

		.align	2


/*
 * Variables used to access the XMS interface
 */

xmshdl:		.word	0		# XMS handle for ram disk
xmsadr:		.long	0		# address of XMS driver interface

extsiz:		.long	0		# new size of extended memory in kB
					# for interrupt 15h interface

/*
 * Variables used to redirect the stack
 */

		.align	2

oldstk:		.long	0		# old stack pointer
newstk:		.space	512,0x90	# new stack for calling XMS driver
newtos:					# new top of stack


/* Signature to put into interrupt vector F1h */
if1sig:		.ascii	"NetB"


/* Flags passed to the boot loader */
ldflags:	.byte	0


/* Descriptor table to access ram disk using the BIOS */
rd_gdt:		.word	0,0,0,0
		.word	0,0,0,0
rd_src:		.word	0xFFFF		# length
rd_srcb:	.byte	0,0,0		# base
		.byte	0x93		# typebyte
		.byte	0		# limit16
rd_srcc:	.byte	0		# base24
rd_dst:		.word	0xFFFF		# length
rd_dstb:	.byte	0,0,0		# base
		.byte	0x93		# typebyte
		.byte	0		# limit16
rd_dstc:	.byte	0		# base24
		.word	0,0,0,0		# BIOS CS
		.word	0,0,0,0		# BIOS DS


/*
 * BIOS disk parameter block. This is actually just a scratch area for
 * our own boot sector.
 */

new_dpb:	.space	DPB_SIZE,0


/*
 * Copy of bootp block from bootrom. This has to be last in the data area
 */

btplen:		.word	0		# length of bootp block
btpnew:					# bootp block has to be at the very end




/*
 *====================================================================
 */
	.end

