use16
org 0

start:
  jmp short code_start
  
; 1K bootloader + 160K tape image (loaded in 3 stages: 64512, 64512, 34816)
; tracks 0.-15. (segment 7800h), tracks 16.-31. (8800h), tracks 32.-39. (9800h)
; 640K RAM required, 479K free afterwards
  
; compile-time constants, disk image is DOS compatible 160K (40 tracks, 1 head, 8 spt, 512 bytes per sector)
OLD_INT13H                 equ 0FCh
IMG_DISK_TRACKS            equ 40d
IMG_DISK_HEADS             equ 1
IMG_DISK_SECTORS           equ 8
SEG_LOADER                 equ 77C0h ; RAM 77C00h to 78000h (1K)
SEG_DRIVE                  equ 7800h ; RAM 78000h to A0000h (160K)
DRIVE_RAM_HIWORD           equ 7     ; hiword of 78000h
DRIVE_RAM_LOWORD           equ 8000h ; loword of 78000h

PCJR                       db 0
RAMERROR                   db 13,10,'640K RAM needed',0

code_start:
  cli
  cld
  xor ax,ax
  mov ss,ax
  mov sp,0FFFFh
  mov ax,cs
  mov ds,ax
  mov es,ax

; check if PC or PCjr
  push ds
  mov ax,0f000h
  mov ds,ax
  mov si,0FFFEh
  lodsb
  pop ds
  cmp al,0FDh
  jne short @f
  mov [PCJR],1
  
@@:    
; test 640K RAM presence by direct write
  push ds
  mov ax,09FFFh
  mov ds,ax
  mov es,ax
  mov di,0Ch
  mov ax,0DEADh
  stosw
  mov ax,0BEEFh
  stosw
  mov si,0Ch
  lodsw
  cmp ax,0DEADh
  jne short ram_fail_640k
  lodsw
  cmp ax,0BEEFh
  jne short ram_fail_640k
  mov ax,640d
  clc
  jmp short @f
ram_fail_640k:
  stc
@@:
  pop ds
  jnc short ram_size_ok
  mov si,RAMERROR
  call print
  sti
  jmp short $
  
WELCOME			               db 13,10,'BASIC 3-stage bootloader, (c) J. Bogin',13,10,'Loading 1st stage...',13,10,0

; are we relocated ?
ram_size_ok:
  mov ax,cs
  cmp ax,SEG_LOADER
  je short begin_tape
  
; patch available RAM to 479K on PC, skip on PCjr that has a memory hole
  mov di,413h
  xor ax,ax
  mov es,ax
  mov ax,479d
  cmp [PCJR],1
  je short @f
  stosw
  
; relocate this 1K code 1K before the start of the bootdisk image
@@:
  mov si,start
  mov ax,SEG_LOADER
  mov es,ax
  push es
  xor di,di
  push di
  mov cx,1024d
  rep movsb
  retf
  
DATAERROR		               db 'Data error',0

; begin tape loading, show cursor
begin_tape:  
  mov ah,1
  mov cx,0607h
  int 10h
	mov si,WELCOME
	call print
  
	; load stage 1 and check result
	mov ax,SEG_DRIVE
	mov es,ax
  xor bx,bx
	mov ax,0200h
	mov cx,64512d
	int 15h
	cmp ah,0
	je short stage_2
	mov si,DATAERROR
	call print
  sti
	jmp short $
  
STAGE2                     db 'Loading 2nd stage...',13,10,0
  
stage_2:
	mov si,STAGE2
	call print
  
	mov ax,87C0h ; SEG_DRIVE + 64512
	mov es,ax
  xor bx,bx
	mov ax,0200h
	mov cx,64512d
	int 15h
	cmp ah,0
	je short stage_3
  mov si,DATAERROR
	call print
  sti
	jmp short $
  
STAGE3                     db 'Loading 3rd stage...',13,10,0
  
stage_3:
  mov si,STAGE3
	call print
  
	mov ax,9780h ; SEG_DRIVE + 64512 + 64512
	mov es,ax
  xor bx,bx
	mov ax,0200h
	mov cx,34816d
	int 15h
	cmp ah,0
	je short boot_tape
	mov si,DATAERROR
	call print
  sti
	jmp $
  
BOOTING                    db 'Booting...',13,10,0

; prepare to boot, wipe ROM BASIC remnants and install our interrupts
boot_tape:
  mov si,BOOTING
	call print
  
	xor ax,ax
	mov es,ax
  mov bx,0F000h
  cmp [PCJR],0
  je short @f
; PCJR specifics: restore INT 0, 4, 9 (keyb IRQ), 0Bh (COM)
  mov ax,0F815h
  mov [es:0],ax
	mov [es:0+2],bx
  mov [es:4*4],ax
	mov [es:4*4+2],bx
  mov [es:0Bh*4],ax
	mov [es:0Bh*4+2],bx
  mov ax,1561h
  mov [es:9*4],ax
	mov [es:9*4+2],bx
; both PC and Jr: restore original INT 1Bh and 1Ch handlers
@@:
  mov ax,0FF53h
  cmp [PCJR],0
  je short @f
  mov ax,0F83Ch
@@:
  mov [es:1Bh*4],ax
	mov [es:1Bh*4+2],bx
  mov [es:1Ch*4],ax
	mov [es:1Ch*4+2],bx
; INT 60h to FFh will point to IRET
  mov bx,60h
  mov cx,2
@@:
  mov di,bx
  shl di,cl
  mov ax,dummy_handler
  stosw
  mov ax,cs
  stosw
  inc bx
  cmp bx,100h
  jne short @b
; clear memory above BDA (1K to 64K)
  xor ax,ax
  mov di,500h
  mov cx,0FB00h
  rep stosb
; INT 13h vector -> save to OLD_INT13H and install new handler
	mov ax,[es:13h*4]
	mov bx,[es:13h*4+2]
	mov [es:OLD_INT13H*4],ax
	mov [es:OLD_INT13H*4+2],bx
	mov ax,shiny_new_int13h
  mov bx,cs
	mov [es:13h*4],ax
	mov [es:13h*4+2],bx
; INT 19h vector -> install new handler
  mov ax,shiny_new_int19h
	mov [es:19h*4],ax
	mov [es:19h*4+2],bx
  
; increment the number of floppies in system, mark bit 0 as available for boot
  xor ax,ax
  mov ds,ax
	mov di,410h
  mov si,di
	lodsw
  mov cx,6
  shr al,cl
  cmp al,2
  ja short @f
  inc al
@@:
  shl al,cl
  mov si,di
  mov bx,ax
  lodsw
  and al,3Fh
  or al,bl
  or al,1
	stosw
	
; boot
  int 19h
  
; print strings
print:
	mov ah,0Eh
	lodsb
	cmp al,00
	je short @f
	int 10h
	jmp short print
@@:
	ret
  
; dummy ISR
dummy_handler:
  iret
  
; new INT 19h handler: trash CS, IP, flags, copy bootsector to 0:7C00h, DL=0 and boot
shiny_new_int19h:
  cli
  pop ax
  pop ax
  pop ax
  mov ax,SEG_DRIVE
  mov ds,ax
	xor si,si
  mov es,si
  push es
	mov di,7C00h
  push di
	mov cx,512d
  xor dl,dl
	rep movsb
  sti
  retf

; new INT 13h handler
shiny_new_int13h:
	pushf
	cmp dl,0
	je short @f
	popf
	dec dl
	int OLD_INT13H
	inc dl
	push bp
	mov bp,sp
	push ax
	lahf
	mov [bp+6],ah
	pop ax
	pop bp
	iret
@@:
	cli
	push ax
	push cx
	push dx
	push bx
	push bp
	push si
	push di
	push ds
	cmp ah,02h
	je short new_int13h_read
	cmp ah,03h
	je short new_int13h_write
	cmp ah,08h
	je short new_int13h_params
	cmp ah,15h
	je short new_int13h_disktype
	jmp short new_int13h_end
; return disk parameters
new_int13h_params:
	mov bp,sp
	mov ch,IMG_DISK_TRACKS
	mov cl,IMG_DISK_SECTORS
	mov al,IMG_DISK_HEADS
	mov [bp+11],al
	mov [bp+12],cx
	jmp short new_int13h_end
; not allowed
new_int13h_write:
	mov bp,sp
	mov ax,1
	or [bp+22],al
	mov ax,3
	mov [bp+15],al
  mov al,0
  mov [bp+14],al
	jmp short new_int13h_end
new_int13h_end:
	pop ds
	pop di
	pop si
	pop bp
	pop bx
	pop dx
	pop cx
	pop ax
	popf
	sti
	iret
; return disk type
new_int13h_disktype:
	mov bp,sp
	mov al,1
	mov [bp+15],al
	mov ax,0
	mov [bp+12],ax
	mov ax,IMG_DISK_TRACKS*IMG_DISK_HEADS*IMG_DISK_SECTORS
	mov [bp+10],ax
  jmp short new_int13h_end
new_int13h_read:
  xor ah,ah
	push ax
	mov di,bx
	xor si,si
	and cl,03Fh
	dec cl
	mov ax,IMG_DISK_SECTORS
	mul dh
	add si,ax
	mov ax,IMG_DISK_HEADS
	mov bl,IMG_DISK_SECTORS
	mul bl
	mul ch
	add si,ax
	xor ch,ch
	add si,cx
  mov ax,512d
  mul si
; DX:AX has RAM offset of buffer from given C/H/S given in CH/DH/CL
  add ax,DRIVE_RAM_LOWORD
  adc dx,DRIVE_RAM_HIWORD
; add SEG_DRIVE * 0x10 to RAM offset
  mov si,10h
  div si
  mov ds,ax
	mov si,dx
; DS:SI contains starting memory location
  pop ax
	mov cx,9
	shl ax,cl
	mov cx,ax
  cld
	rep movsb
  mov bp,sp
  mov [bp+14],dl
  mov al,0
  mov [bp+15],al
	jmp short new_int13h_end

; maximum size of this bootloader is 1K
times 1024-($-$$) db 0