/* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1997 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
/* Routine to load V2.0 format image.  Control is returned to the caller so
   additional setup can be done before execution.
   C. Sandmann Jan-94
 */
#include <libc/stubs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dpmi.h>
#include <go32.h>
#include <signal.h>
#include <setjmp.h>
#include <stubinfo.h>
#include <debug/v2load.h>
#include <libc/farptrgs.h>
#include <sys/stat.h>
#define SIMAGIC "go32stub"

#define __tb_size _go32_info_block.size_of_transfer_buffer

AREAS areas[MAX_AREA];

extern char **environ;
extern unsigned _stklen;

static int read_section(int pf, unsigned ds, unsigned foffset, unsigned soffset, unsigned size)
{
  char buffer[32768];
  int read_size;
  int nr;
  unsigned my_ss;

  size += (foffset & 0x1ff);	/* sector alignment */
  foffset &= 0xfffffe00U;
  soffset &= 0xfffffe00U;
  lseek(pf, foffset, 0);
  asm("mov %%ss,%w0" : "=g" (my_ss) );
  while(size) {
    read_size = (size > sizeof(buffer)) ? sizeof(buffer) : size;
    nr = read(pf, buffer, read_size);
    if(nr != read_size)
      return -1;
    movedata(my_ss, (unsigned)&buffer, ds, soffset, read_size);
    size -= read_size;
    soffset += read_size;
  }
  return 0;
}

int v2loadimage(const char *program, const char *cmdline, jmp_buf load_state)
{
  unsigned short header[5];
  int pf,i, envp;
  unsigned coff_offset,exe_start;
  _GO32_StubInfo si;
  unsigned long coffhdr[42];
  unsigned start_eip, text_foffset, text_soffset, text_size, data_foffset;
  unsigned data_soffset, data_size, bss_soffset, bss_size;
  unsigned client_cs, client_ds, my_ds;
  __dpmi_meminfo memblock;
  unsigned new_env_selector;
  char true_name[FILENAME_MAX];

  _truename(program, true_name);

  pf = open(program, O_RDONLY|O_BINARY);
  if(pf < 0)
    return -1;

  read(pf, header, sizeof(header));
  if (header[0] == 0x5a4d) {		/* MZ exe signature, stubbed? */
    coff_offset = (long)header[2]*512L;
    if (header[1])
      coff_offset += (long)header[1] - 512L;
    exe_start = (unsigned)header[4]*16;
    lseek(pf, exe_start, 0);			/* Position of V2 stubinfo */
    read(pf, si.magic, 8);
    if (memcmp(SIMAGIC, si.magic, 8) != 0) {
      close(pf);
      return -1;				/* Not V2 image, show failure */
    }
    read(pf, &si.magic[8], sizeof(si)-8);	/* Rest of stubinfo */
    /* Check for real image, (for now assume imbedded) */
    lseek(pf, coff_offset, 0);
    read(pf, header, sizeof(header));
  } else {
    coff_offset = 0;
    strcpy(si.magic, "go32stub, V 2.00");
    si.size = 0x44;
    /* The default size of the stack and the transfer buffer are taken
       from the debugger's values, so that users could get predictable
       results by stubediting the debugger.  */
    si.minstack = _stklen;
    si.minkeep = __tb_size;		/* transfer buffer size */
    memset(&si.basename, 0, 24);	/* Asciiz strings */
  }
  if (header[0] != 0x014c) {		/* COFF? */
    close(pf);
    return -1;				/* Not V2 image, show failure */
  }
  lseek(pf, coff_offset, 0);
  i = read(pf, coffhdr, 0x0a8);    
  if (i != 0x0a8) {
    close(pf);
    return -1;				/* Not V2 image, show failure */
  }

  start_eip = coffhdr[5 + 4];
  text_foffset = coff_offset + coffhdr[12 + 5];	/* scnptr */
  text_soffset = coffhdr[12 + 3];		/* vaddr */
  text_size = coffhdr[12 + 4];			/* size */
  areas[A_text].first_addr = text_soffset;
  areas[A_text].last_addr = text_soffset + text_size - 1;
  data_foffset = coff_offset + coffhdr[22 + 5];
  data_soffset = coffhdr[22 + 3];
  data_size = coffhdr[22 + 4];
  areas[A_data].first_addr = data_soffset;
  areas[A_data].last_addr = data_soffset + data_size - 1;
  bss_soffset = coffhdr[32 + 3];
  bss_size = coffhdr[32 + 4];
  areas[A_bss].first_addr = bss_soffset;
  areas[A_bss].last_addr = bss_soffset + bss_size - 1;
  areas[A_stack].first_addr = bss_soffset + bss_size;
  areas[A_stack].last_addr = areas[A_stack].first_addr + si.minstack - 1;
  areas[A_arena].first_addr = areas[A_stack].last_addr + 1;
  areas[A_arena].last_addr = (areas[A_arena].first_addr & ~0xffff) + 0xffff;

  si.initial_size = bss_soffset + bss_size;
  if (si.initial_size <= 0x10001)
    si.initial_size = 0x10001;
  si.initial_size += 0xffff;
  si.initial_size &= 0xffff0000U;

  si.env_size = 0;
  for (i=0; environ[i]; i++)
    si.env_size += strlen(environ[i])+1;
  si.env_size += 4 + strlen(true_name);

  /* Allocate the dos memory for the environment and command line. */
  i = __dpmi_allocate_dos_memory((si.env_size + 256) / 16, (int *)&new_env_selector);
  if(i == -1)
    return -1;

  envp = 0;
  for (i=0; environ[i]; i++)
  {
    int len = strlen(environ[i])+1;
    movedata(_my_ds(), (unsigned)environ[i], new_env_selector, envp, len);
    envp += len;
  }
  _farpokeb(new_env_selector, envp++, 0);
  _farpokeb(new_env_selector, envp++, 1);
  _farpokeb(new_env_selector, envp++, 0);
  movedata(_my_ds(), (unsigned)true_name, new_env_selector, envp, strlen(true_name)+1);

  /* Allocate the dos memory for the transfer buffer.  This nukes si.cs_selector,
   but that's OK since we set it next. */
  i = __dpmi_allocate_dos_memory((si.minkeep + 256) / 16, (int *)&si.psp_selector);
  if(i == -1)
    return -1;
  si.ds_segment = i + (256/16);

  if((si.cs_selector = __dpmi_allocate_ldt_descriptors(1)) == 0xffff)
    return -1;

  if(__dpmi_set_segment_base_address(si.cs_selector, si.ds_segment * 16) == -1)
    return -1;

  /* Set the type as 16 bit, small */
  if(__dpmi_set_descriptor_access_rights(si.cs_selector, ((si.cs_selector & 3) << 5) | 0x009b) == -1)
    return -1;

  if(__dpmi_set_segment_limit(si.cs_selector, si.minkeep-1) == -1)
    return -1;

  if((si.ds_selector = __dpmi_create_alias_descriptor(si.cs_selector)) == 0xffff)
    return -1;

  if(__dpmi_set_descriptor_access_rights(si.ds_selector, ((si.cs_selector & 3) << 5) | 0x4093) == -1)
    return -1;

#ifdef DEBUG
  show_selector("ds16", si.ds_selector);
  show_selector("cs16", si.cs_selector);
  show_selector("psp16", si.psp_selector);
  printf("mine:\n");
  show_selector("ds16", _stubinfo->ds_selector);
  show_selector("cs16", _stubinfo->cs_selector);
  show_selector("psp16", _stubinfo->psp_selector);
  printf("Initial size: 0x%lx\n",si.initial_size);
#endif

/* Finished with 16 bit config, now for 32 bit setup */

  if((client_cs = __dpmi_allocate_ldt_descriptors(1)) == 0xffff)
    return -1;

  memblock.size = si.initial_size;
  if(__dpmi_allocate_memory(&memblock) == -1)
    return -1;
  si.memory_handle = memblock.handle;
  
  if(__dpmi_set_segment_base_address(client_cs, memblock.address) == -1)
    return -1;

  /* Set the type as 32 bit, big */
  if(__dpmi_set_descriptor_access_rights(client_cs, ((client_cs & 3) << 5) | 0xc09b) == -1)
    return -1;

  if(__dpmi_set_segment_limit(client_cs, si.initial_size-1) == -1)
    return -1;

  if((client_ds = __dpmi_create_alias_descriptor(client_cs)) == 0xffff)
    return -1;

/* This shouldn't be necessary, but QDPMI does not clone the 32 bit properly
   so we have to reset this stuff or it doesn't work when it is used as the
   client's SS  :-( */
  if(__dpmi_set_descriptor_access_rights(client_ds, ((client_cs & 3) << 5) | 0xc093) == -1)
    return -1;

  if(__dpmi_set_segment_limit(client_ds, si.initial_size-1) == -1)
    return -1;
/* End should be unnecessary code */

/* Load image */
  my_ds = 0;
  asm("mov %%ds,%w0" : "=g" (my_ds) );
#ifdef DEBUG
  show_selector("my_ds", my_ds);
  show_selector("client_ds",client_ds);
  asm("mov %%cs,%w0" : "=g" (i) );
  show_selector("my_cs",i);
  show_selector("client_cs",client_cs);
  printf("Load image...\n");
#endif

  if(read_section(pf, client_ds, text_foffset, text_soffset, text_size) )
    return -1;
  if(read_section(pf, client_ds, data_foffset, data_soffset, data_size) )
    return -1;
  for (i=0; i+3<bss_size; i+= 4)
    _farpokel(client_ds, bss_soffset+i, 0);
  for (; i<bss_size; i++)
    _farpokeb(client_ds, bss_soffset+i, 0);

  close(pf);
  /* copy my psp into debug process */
  movedata(_stubinfo->psp_selector, 0, si.psp_selector, 0, 128);

  _farpokew(si.psp_selector, 0x2c, new_env_selector);

  /* copy command arguments into debug process */
  movedata(my_ds, (unsigned)cmdline, si.psp_selector, 128, 128);
  /* copy si we built into debug process */
  movedata(my_ds, (unsigned)&si, si.ds_selector, 0, sizeof(si));
  load_state->__ds = client_ds;
  load_state->__cs = client_cs;
  load_state->__eip = start_eip;
  load_state->__esp = si.minkeep;
  load_state->__ss = si.ds_selector;
  load_state->__fs = si.ds_selector;
  load_state->__es = load_state->__gs = client_ds;
  load_state->__eflags = 0x3202;
  return 0;
}

#ifdef TEST
void main(int argc, char **argv)
{
  int i;
  char cmdline[128];
  jmp_buf load_state;

  if(argc < 2) {
    printf("Usage: v2load imagename [args]\n");
    exit(1);
  }

  putenv("FOO=from-v2load");
  
  cmdline[1] = 0;
  for(i=2; argv[i]; i++) {
  	strcat(cmdline+1, " ");
  	strcat(cmdline+1, argv[i]);
  }
  i = strlen(cmdline+1);
  cmdline[0] = i;
  cmdline[i+1] = 13;
  if(v2loadimage(argv[1],cmdline,load_state)) {
    printf("Load failed for %s\n",argv[1]);
    exit(2);
  }
  printf("Program text ................: %08x - %08x\n",
	     areas[A_text].first_addr, areas[A_text].last_addr);
  printf("Program data ................: %08x - %08x\n",
	     areas[A_data].first_addr, areas[A_data].last_addr);
  printf("Program bss .................: %08x - %08x\n",
	     areas[A_bss].first_addr, areas[A_bss].last_addr);
  printf("Program stack ...............: %08x - %08x\n",
	     areas[A_stack].first_addr, areas[A_stack].last_addr);
  printf("Program arena ...............: %08x - %08x\n",
	     areas[A_arena].first_addr, areas[A_arena].last_addr);
  printf("Jumping to image...\n");
  longjmp(load_state, 0);
}
#endif
