{
	Stub Loader Copyright Rossi Gianluca [ranger@racine.ra.it]

log history :
V0.0 tiny assembler version
V0.1 Turbo C version
V0.2 TC version bug fix
V0.3 TC / MSC
V0.4 TC / MSC, added command line for kernel
V0.5 Pascal & Nasm, reduced size [about from 20K to 10K]
V0.6 P&N addedd paging for virtual address != 0
V0.61 alloc dos memory & bug correction


[NOTE] la dimensione del file caricato deve essere < 1M :)
}

{$M $400,0,0}
{$I-,W-,G+,V-,S-,R-,Q-}


{***$DEFINE NDEBUG}
function XMS_check: boolean; external;
function XMS_init: boolean; external;
procedure XMS_clean; external;
procedure XMS_move; external;
procedure XMS_switch; external;
procedure kernel_buf_setup; external;
{$L nxms.obj}

type string9 = string[9];

const cmdl_debug: integer = 0;
const cmdl_text: integer = 0;
const cmdl_fname: string[64] = 'a.out';
const cmdl_pages: word = 0;
const cmdl_kernel: string[64] = '';
const _dos_mem: pointer = nil;

var
{ codici di errore del driver specifico (XMS, raw, vcpi) }
    driver_code: word;
{ procedura per spostare memoria }
    server_move_mem: function (vaddr: longint; var from; size: word): boolean;
{ procedura di switch }
    server_switch: function(eip: longint): integer;
{ buffer utilizzato per la comunicazione }
    kernel_buffer: array [0.. 255] of byte;
{ base in cui deve essere caricato il kernel }
    virtual_base: longint;


var lastmode: word;

procedure set_rows(row: integer);
begin
	if (row = 49) then
        asm
        	mov 	ax,1112h
                xor	bx,bx
                int	10h
        end
        else
        asm
        	mov	ax,3
                int	10h
        end;

        asm
        	mov	ax,600h
                mov	bx,0700h
                mov	cx,0
                mov     dh,row.byte
                mov	dl,79
                int	10h

                xor	dx,dx
                mov	ah,2
                xor	bh,bh
                int	10h
        end;

end;

function get_rows: word; assembler;
asm
        push	es
	mov	ax,40h
        mov	es,ax
        mov	al,es:[84h]
        mov	ah,0
        pop	es
end;

procedure help;
begin
	writeln('stub loader V0.61 copyright Rossi Gianluca [ranger@racine.ra.it]');
        writeln;
        writeln('use: stub [-d -v -p# -l"ARG"] -fNAME"');
        writeln('	-d	set debug mode [debug version only]');
        writeln('	-v	80x50 text mode');
        writeln('	-pNNN   reserve NNN memory pages [default all memory]');
        writeln('	-l"ARG" set loaded program command line');
        writeln('        -fNAME  set file name [default a.out]');
        writeln;
        halt(1);
end;

procedure abort;
begin
        set_rows(lastmode);
	writeln('Abort!');
	halt(3);
end;


procedure errors(const s: string);
begin
        set_rows(lastmode);
	writeln(s);
        halt(2);
end;

procedure ioerror;
var b: byte;
begin
        b := IOResult;
        if (b <> 0) then
	  begin
            set_rows(lastmode);
	    writeln('I/O error #',b);
            halt(2);
          end;
end;

procedure memerror;
begin
	errors('not enought memory');
end;

procedure drivererror;
begin
	set_rows(lastmode);
   	writeln('driver error #', driver_code);
        halt(2);
end;

function hexstr(l: longint): string9;
const hs: string[16] = '0123456789ABCDEF';
var
      r: string9;
      i: integer;
begin
  for i := 1 to 8 do
  begin
    r[9 - i] := hs[((l shr ((i-1)*4)) and $F) + 1];
  end;
  r[0] := #9;
  r[9] := 'h';
  hexstr := r;
end;


{
	parser per la linea di comando, gli switch validi sono :
        -d per debug
        -v per 80x50
        -pNNN per indicare il numero di pagine da allocare.
              NNN puo` essere un numero decimalo o esadecimale
              in formato pascal,C o assembler

        -larg1
         oppure
        -l"arg1 arg2..." indica quali argomenti passare al programma
        -fNOME indica quale file caricare

}
procedure parse_cmdl;

        {
         riconosce almeno una stringa esadecimale nel formato :
          $nnn    (pascal)
          0xnnn   (c)
          nnnh    (assembler)
          come effetto collaterale funzionano anche :
          $nnnh
          0xnnnh
        }
	function hstr2int(const s: string): longint;
        var v,i:longint;
        begin
                i := 1;
                v := 0;
                if (s[1] = '$') then inc(i)
                else if (s[1] = '0') and (s[2] = 'x') then inc (i,2);

                while ((upcase(s[i]) in ['0' .. '9', 'A' .. 'F']) and (i < length(s))) do
                begin
                  if (s[i] in ['0' .. '9']) then v := v*16+(byte(s[i]) - byte('0'))
                  else v := v*16+(byte(s[i]) - byte('A')+10);
                  inc(i);
                end;
                if (i = length(s)) or ((upcase(s[i]) = 'H') and (length(s) = i+1)) then hstr2int := v
                else
                begin
                  errors('invalid number');
                end;
        end;

	function str2int(const s: string): longint;
        var i: longint;
            c: integer;
        begin
        	val(s,i,c);
                if (c <> 0) then i := hstr2int(s);
                str2int := i;
        end;

var s: string[64];
    i: integer;
    l: longint;
begin
	for i := 1 to paramcount do
        begin
        	s := paramstr(i);
        	if (s[1] = '/') or (s[1] = '-') then
                begin
                	case s[2] of
                         'h','?': help;
                         {$IFNDEF NDEBUG}
                         'd' : cmdl_debug := 1;
                         {$ENDIF}
                         'v' : cmdl_text := 1;
                         'f' : begin
			 	 cmdl_fname := s;
                                 delete(cmdl_fname, 1, 2);
                               end;
                         'p' : begin
                                 delete(s, 1, 2);
				 l := str2int(s);
                                 if (l > $3ff0) then
                                 begin
                                   errors('too many pages (max 3ff0h)');
                                 end;
                                 cmdl_pages := l;
                               end;
                         'l' : begin
                                 if (s[3] <> '"') then
                                 begin
                                   delete(s, 1, 2);
                                   if (length(s) = 0) and (paramcount > i) then
                                   begin
                                   	inc(i);
                                   	s := paramstr(i);
                                   end;
                                   cmdl_kernel := s;
                                   if length(cmdl_kernel) = 0 then
                                   begin
                                     errors('no args after -l');
                                   end;
                                 end
                                 else
                                 begin
                                   delete(s, 1, 3);
                                   cmdl_kernel := s;
                                   inc(i);
                                   s := paramstr(i);
                                   while (pos('"', s) <> length(s)) do
                                   begin
                                     cmdl_kernel := concat(cmdl_kernel, ' ',s);
                                     inc(i);
                                     s := paramstr(i);
                                   end;
                                   cmdl_kernel := concat(cmdl_kernel, ' ',s);
                                   if (cmdl_kernel[length(cmdl_kernel)] = '"') then dec(cmdl_kernel[0]);
                                 end;
                               end
                       end;

                end
                else
                begin
                        errors('invalid switch : '+s);
                end
        end

end;



function fileopen(var s: string) : word;
begin
  s[length(s) +1] := #0;
  asm
  	push	ds
	xor	cx,cx
  	mov 	ah,3dh
        mov	al,filemode
        lds 	dx,s
        inc	dx
  	int 	21h
        pop	ds
        jnc	@@1
        mov     InOutres,ax
        xor	ax,ax
        @@1:
        mov	@result,ax
  end;
end;

function fileread(h: word; var buf; count: word): word;
begin
  asm
        push	ds
        mov	ah,3fh
        mov	bx,h
        mov	cx,count
        lds	dx,buf
        int	21h
        pop	ds
        jnc	@@1
        mov	inOutres,ax
        xor	ax,ax
        @@1:
        mov	@result,ax
  end
end;

procedure fileclose(h: word);
begin
  asm
  	mov	ah,3eh
        mov	bx,h
        int	21h
  end
end;

const F_START = 0;
      F_CUR = 1;
      F_END = 2;

function fileseek(h: word; fofs: longint; from: byte): longint;
begin
  asm
  	mov	bx,h
        mov	al,from
        mov	ah,42h
        mov	dx,fofs.word
        mov	cx,fofs.word[2]
        mov	bx,h
        int	21h
        jnc	@@1
        mov	inOutres,ax
        xor	ax,ax
        xor	dx,dx
        dec	ax
        dec	dx
        @@1:
        mov	@result.word,ax
        mov	@result.word[2],dx
  end;
end;

function xmalloc(size: longint): pointer;
begin
	size := (size + 15) shr 4;
  asm
  	mov	ah,48h
        mov	bx,size.word[0]
        int	21h
        jnc	@@1
        call	memerror
        @@1:
        mov	@result.word[2],ax
        mov	@result.word,0
  end;
end;

procedure xfree(p: pointer);
begin
  asm
  	mov	ah,49h
        mov	es,p.word[2]
        int	21h
  end;
end;



procedure sread(h:word; ofs: longint; vaddr: longint; size: longint);
const MEM_SZ = 1024;
var p: pointer;
    r: word;
begin
	fileseek(h, ofs, F_START);
        ioerror;
        p := xmalloc(MEM_SZ);

        while (size > 0) do
        begin
        	r := fileread(h, p^, MEM_SZ);
                ioerror;
                if r = 0 then abort;
                if (not server_move_mem(vaddr, p^, r)) then
                begin
                        drivererror;
                end;
                dec(size, r);
                inc(vaddr, r);
        end;

        xfree(p);
end;



function load_coff: longint;

	function strcmp(var _a; const b:string): boolean;
        var r: boolean;
            i: integer;
            a: array [1..8] of char absolute _a;
	begin
          r := true;
          for i := 1 to length(b) do
          begin
            r := r and (a[i] = b[i]);
          end;
          strcmp := r;
	end;

type hdrt = record
       magic:	word;
       nsec:	word;
       timed:	longint;
       fsymptr:	longint;
       nsym:	longint;
       hdrsize:	word;
       flags:	word;
       magic2:	word;
       ver:	word;
       tsz:	longint;
       dsz:	longint;
       bsz:	longint;
       eip:	longint;
       tst:	longint;
       dst:	longint;

       text:	array[1..8] of char;
       tpa:	longint;
       tva:	longint;
       tsize:	longint;
       tfptr:	longint;
       tfrel:	longint;
       tflnum:	longint;
       trelocs:	word;
       tline:	word;
       tflags:	longint;

       data:	array[1..8] of char;
       dpa:	longint;
       dva:	longint;
       dsize:	longint;
       dfptr:	longint;
       dfrel:	longint;
       dflnum:	longint;
       drelocs:	word;
       dline:	word;
       dflags:	longint;

       bss:	array[1..8] of char;
       bpa:	longint;
       bva:	longint;
       bsize:	longint;
       bfptr:	longint;
       bfrel:	longint;
       bflnum:	longint;
       brelocs:	word;
       bline:	word;
       bflags:	longint;

     end;

var h: word;
    hdr: hdrt;
begin
  	h := fileopen(cmdl_fname);
        if (IOResult <> 0) then errors('file not found');
        fileread(h, hdr, sizeof(hdr));
        ioerror;
        if ((hdr.magic <> $14C) or
           (hdr.magic2 <> $10B) or
           (hdr.nsec <> 3) or
           (hdr.hdrsize <> 28) or
           (strcmp(hdr.text, '.text') = false) or
           (strcmp(hdr.data, '.data') = false) or
           (strcmp(hdr.bss, '.bss') = false)) then errors('invalid COFF');
        if (hdr.trelocs <> 0) or (hdr.trelocs <> 0) then errors('relocation not supported');

        virtual_base := hdr.tva;

        {$IFNDEF NDEBUG}
        if cmdl_debug = 1 then
        begin
        	writeln('COFF file "', cmdl_fname,'" info :');
        	writeln('.text vaddr ', hexstr(hdr.tva));
                writeln('      size  ', hexstr(hdr.tsize));
                writeln('.data vaddr ', hexstr(hdr.dva));
                writeln('      size  ', hexstr(hdr.dsize));
                writeln('.bss  vaddr ', hexstr(hdr.bva));
                writeln('      size  ', hexstr(hdr.bsize));
                writeln('eip         ', hexstr(hdr.eip));
        end;
        {$ENDIF}

        if (hdr.bva - hdr.tva + hdr.bsize + $FFF) < cmdl_pages*longint(4096) then
        begin
        	sread(h, hdr.tfptr, hdr.tva and $FFFFF, hdr.tsize);
        	sread(h, hdr.dfptr, hdr.dva and $FFFFF, hdr.dsize);
        	fileclose(h);
		load_coff := hdr.eip;
        end
        else memerror;
end;



function VCPI_check: boolean;
begin
  VCPI_check := false;
end;


function RAW_check: boolean;
begin
  RAW_check := false;
end;

procedure check_sys;
begin
	if (VCPI_check) then
        begin
        end
        else if (XMS_check) then
        begin
        	exitproc := @XMS_clean;
                @server_move_mem := @XMS_move;
                @server_switch := @XMS_switch;
        	if (XMS_init = false) then
                begin
                	drivererror;
                end;
        end
        else if (RAW_check) then
        begin
        end
        else errors('cpu already in V86!');
end;

{
  mappa l'indirizzo virtual_base addr all'indirizzo phaddr,
  ed il primo Mb 1:1.
  NOTE: questa procedura viene eseguita quando si e` gia` in pmode !!
}
function map_memory_and_getcr3(phaddr: longint): integer;
var
  pde: array [0..1023] of longint absolute $ba00:0; { directory di pagina }
  pte1: array [0..1023] of longint absolute $bb00:0; { mappa primi 4Mb }
  pte2: array [0..1023] of longint absolute $bc00:0; { mappa altri 4Mb }
  pte3: array [0..1023] of longint absolute $bd00:0; { mappa altri 4Mb }
  pte4: array [0..1023] of longint absolute $be00:0; { mappa altri 4Mb }
  pte5: array [0..1023] of longint absolute $bf00:0; { mappa altri 4Mb }
  pdi, pti, i: integer;
  l : longint;

begin

  pdi := virtual_base shr 22;
  pti := (virtual_base shr 12) and $3FF;

  {$IFNDEF NDEBUG}
  if (cmdl_debug = 1) then
  begin
  	writeln('memory mapping stats : ');
        writeln('virtual address ',hexstr(virtual_base),' phisical address ', hexstr(phaddr));
        writeln('cr3 = ',hexstr(seg(pde) * longint(16)));
  end;
  {$ENDIF}
  if (pti <> 0) then errors('virtual address not 4Mb aligned!!');

  if (phaddr > $C00000) then
  begin
  	errors('base address must be < 12Mb');
  end;

  { **** mappa il primo Mb (256 pagine) ****}
  fillchar(pde, 1024, 0);
  fillchar(pte1, 1024, 0);
  fillchar(pte2, 1024, 0);


    { questo sarebbe : ((seg(pte1) * 16) shr 12) shl 12; }
  pde[0] := 3 + (seg(pte1) shl longint(4));
  pde[1] := 3 + (seg(pte2) shl longint(4));
  pde[2] := 3 + (seg(pte3) shl longint(4));
  pde[3] := 3 + (seg(pte4) shl longint(4));
  {
  l := 0;
  for i := 0 to 1023 do
  begin
    pte1[i] := 3 + (l shl 12);
    inc (l);
  end;


  for i := 0 to 1023 do
  begin
    pte2[i] := 3 + (l shl 12);
    inc (l);
  end;


  for i := 0 to 1023 do
  begin
    pte3[i] := 3 + (l shl 12);
    inc (l);
  end;


  for i := 0 to 1023 do
  begin
    pte4[i] := 3 + (l shl 12);
    inc (l);
  end;
  }
  l := 0;
  for i := 0 to 1023 do
  begin
    pte1[i] := 3 + l;
    pte2[i] := $400003 + l;
    pte3[i] := $800003 + l;
    pte4[i] := $C00003 + l;
    inc(l, $1000);
  end;



  pde[pdi] := 3 + (seg(pte5) shl longint(4));
  l := phaddr;
  for i := 0 to 1023 do
  begin
    pte5[i] := 3 + l;
    inc(l, $1000);
  end;

  map_memory_and_getcr3 := seg(pde);
end;



var ec: integer;
    eip: longint;
begin
        lastmode := get_rows;
	if (Test8086 < 2) then errors('386 required');
        _dos_mem := xmalloc($ffff);
  	parse_cmdl;
        check_sys;

        if (cmdl_text = 1) and (lastmode <> 49) then set_rows(49);
        if (cmdl_text = 0) and (lastmode <> 24) then set_rows(24);

        eip := load_coff;
        kernel_buf_setup;
        ec := server_switch(eip);
        {$IFNDEF NDEBUG}
        if (cmdl_debug = 1) then
	begin
          asm
          	xor ax,ax
                int 16h
          end;
	end;
        {$ENDIF}
        set_rows(lastmode);
        {$IFNDEF NDEBUG}
        if (cmdl_debug = 1) then writeln('exit code #',ec);
        {$ENDIF}
        halt(ec);
end.