/*
 * Novell loadable module decoder
 *
 * by W.J.Hengeveld  jun 1994
 *    itsme@xs4all.nl
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <io.h>
#include <fcntl.h>

#define BLOCKSIZE 16384
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;
//
// todo:
//   add extra option for leftover of file (message file)
//
/*
 00   - char[0x18] : 'NetWare Loadable Module' 1a
 18   - long       : 00000004        ; load version
          04  = NW 3.11
          84  = NW 3.12 & above (NLM compression)
 1c   - strlen:1, string:strlen,  00:(13-strlen)
 2a   - offset, len     ; 0, 1 code segment
 32   - offset, len     ; 2, 3 data segment
 3a   - 0(long)         ; 4    length of uninitialized data
 3e   - offset, len     ; 5, 6 custom data segment
 46   - offset, nrec    ; 7, 8 autoload modules
 4e   - offset, nrec    ; 9,10 fixups
 56   - offset, nrec    ;11,12 externals
 5e   - offset, nrec    ;13,14 exported symbols
 66   - offset, nrec    ;15,16 debug symbols
 6e   - codeoffset      ;17    start nlm
 72   - codeoffset      ;18    terminate nlm
 76   - codeoffset      :19    check nlm
 7a   - type            :20
          00=nlm  NetWare Loadable Module
          01=lan  LAN Driver
          02=dsk  Disk Driver
          03=nam  Name Space
          04=msl  Mirrored Server Link
          05=
          06=nw3  Operating system
          07=nw4  Operating system
          0c=internal  'hidden' internal NLM, can not be unloaded
 7e   - ?               ;21

 82   - len, string, 00                          ; description
   - 00 20 00 00 00 00 00 00 20      ; flags
   - 'LONG'
   - len, string, 00                          ; screenname
   - len, string, 00                          ; threadname
   - 'VeRsIoN#'
   -   major           (long)
   -   minor           (long)
   -   sub version     (long)
   -   year
   -   month
   -   day

   - 'CoPyRiGhT='
   - len, string,0    ; copyright message

   - 'MeSsAgEs'
   -  LONGS ...

types in external symbol list:
0  - ? relative reference (near call) from data segment
4  - relative reference (near call) from code segment
8  - ? absolute reference (long offset) from data segment
c  - absolute reference (long offset) from code segment

types in fixup table:
0  - abs ref to data segment from data segment
4  - abs ref to data segment from code segment
8  - abs ref to code segment from data segment
c  - abs ref to code segment from code segment
*/

int verbose=FALSE;      // for full header dump
int extractall=FALSE;   // extract all parts to default files

struct NlmHeader {
  char signature[24];    // "NetWare Loadable Module" 1a
  long loader;           // 04=normal  84=packed
  char name[14];         // length preceded nlm name
  long code, codeLen;
  long data, dataLen;
  long unInitLen;
  long custom, customLen;
  long autoLoad, Nautoload;
  long fixup, Nfixup;
  long external, Nexternal;
  long export, Nexport;
  long debug, Ndebug;
  long NLM_load;
  long NLM_unload;
  long NLM_check;
  long type;             // see NLMTypes array
  long unknown;
} NLMhdr;

#define MAX_NLMTYPE (sizeof(NLMTypes)/sizeof(char *))

char NLMTypes[MAX_NLMTYPE]={
  "nlm", "lan", "dsk", "nam", "msl", NULL,  "nw3", "nw4",
  NULL,  NULL,  NULL,  NULL,  "hid", NULL,  NULL,  NULL
};

struct info {
  long header_start, header_end;
  long code_start, code_end;
  long data_start, data_end;
  long reloc_start, reloc_end;
  long import_start, import_end;
  long exported_code_start, exported_code_end;
  long modules_start, modules_end;
  long custom_start, custom_end;
  long debug_start, debug_end;
  long file_end;
} inforec;

enum fileid {
  F_NLM,              source file
  F_CODE,             for code image
  F_DATA,             for data image
  F_CUSTOM,           for custom data
  F_EXTRA,            for extra text
  F_LIST,             for full dump of nlm info
  NR_FILEIDS
};

struct outfile {
  char *fn;
  char *ext;
  long &start, long &end;
} outfiles[5]={
  NULL, ".nlm", NULL, NULL,
  NULL, ".cod", &inforec.code_start, &inforec.code_end,
  NULL, ".dat", &inforec.data_start, &inforec.data_end,
  NULL, ".cus", &inforec.custom_start, &inforec.custom_end,
  NULL, ".txt", &inforec.debug_end, &inforec.file_end,
  NULL, ".lst", NULL, NULL
};

long dump(long ofs, int f, long n)
{
  long i;
  unsigned char c;
  for (i=0 ; i<n ; i++)
  {
    if ((i&15)==0)
      printf("%05lx : ",ofs);
    read(f,&c,1);     ofs++;
    printf(" %02x",c);
    if ((i&15)==15)
      putchar('\n');
  }
  if (i&15) putchar('\n');
  return(ofs);
}

long dumpt(long ofs, int f, long n)
{
  long i;
  unsigned char c;
  for (i=0 ; i<n ; i++)
  {
    if ((i&31)==0)
      printf("%05lx : \"",ofs);
    read(f,&c,1);  ofs++;
    if (c>=' ' && c<='~')
      putchar(c);
    else
      printf(" \\%02x",c);
    if ((i&31)==31)
      printf("\"\n");
  }
  if (i&31) printf("\"\n");
  return(ofs);
}

// if tab!=NULL -> longs are stored
// if msg!=NULL -> longs are printed followed by message
long dumpl(long ofs, int f, long n, long *tab,char *msg)
{
  long i;
  long l;
  for (i=0 ; i<n ; i++)
  {
    if (msg && (i%6)==0)
      printf("%05lx : ",ofs);
    read(f,&l,sizeof(long));  ofs+=4;
    if (tab!=NULL) *tab++=l;
    if (msg) printf("%08lx  ",l);
    if (msg && (i%6)==5)
      printf("    %s\n",msg);
  }
  if (msg && i%6)  printf("    %s\n",msg);
  return(ofs);
}

// prints <n> strings of format
//   <len:1> <chars:len>
long dumps(long ofs, int f, long n)
{
  unsigned char l;
  char *p;
  while (n--)
  {
    read(f,&l,1);
    p=malloc(l+1);
    read(f,p,l);
    p[l]=0;
    printf("%05lx : %02x:\"%s\"",ofs,l,p);
    free(p);
    ofs+=1+l;
    if (n) putchar('\n');   // only newline if more than 1 string
  }
  return(ofs);
}

long dumpsn4(long ofs, int f, long n)
{
  long i;
  long k;
  for (i=0 ; i<n ; i++)
  {
    ofs=dumps(ofs,f,1L);  putchar('\n');
    read(f,&k,sizeof(long)); ofs+=sizeof(long);
    printf("%05lx   * %08lx\n",ofs,k);
    ofs=dumpl(ofs,f,k,NULL,"");
  }
  return(ofs);
}

long dumpb4s(long ofs, int f, long n)  /* dump debug info table */
{
  long i;
  byte  x;
  long  k;
  byte  l;
  byte  c;
  for (i=0 ; i<n ; i++)
  {
    read(f, &x, 1);
    read(f, &k, 4);
    read(f, &l, 1);
    printf("%05lx   %02x %08lx %02x:", ofs, x, k, l);
    ofs+=6;
    while (l--)
    {
      read(f, &c, 1);  ofs++;
      putchar(c);
    }
    putchar('\n');
  }
  return(ofs);
}

// dump exported locals table
long dumps4(long ofs, int f, long n)
{
  long i;
  unsigned char slen;
  char *p;
  long local;
  for (i=0 ; i<n ; i++)
  {
    read(f,&slen,1);
    p=malloc(slen+1);
    read(f,p,slen);
    p[slen]=0;
    read(f,&local,sizeof(long));
    printf("%05lx : %08lx %02x:\"%s\"\n",ofs,local,slen,p);
    free(p);
    ofs+=5+slen;
  }
  return(ofs);
}

// dumps special strings line version, copyright & messages
long dumpSpecial(long ofs, int f, long max)
{
  long n=0;
  char name[40];
  char c;
  int end=0;
  long ver[6];

  while (!end && ofs<max)
  {
    memset(name,0,sizeof(name));
    printf("%05lx : \"", ofs);
    n=0;
    do {
      read(f, &c, 1);  ofs++;
      name[n++]=c;  putchar(c);

      if (strcmp(name,"VeRsIoN#")==0)
      {
        ofs=dumpl(ofs,f,6L, ver,NULL);
        printf("\" %ld.%02ld%c", ver[0], ver[1], ver[2] ? ver[2]+0x60 : ' ');
        printf("  %ld-%02ld-%02ld\n", ver[3], ver[4], ver[5]);
        end=0;
        break;
      } else if (strcmp(name,"CoPyRiGhT=")==0)
      {
        printf("\"\n");
        ofs=dumps(ofs,f,1L);
        read(f, &c, 1);  ofs++;
        printf(", %02x\n", c);    // terminating 0
  //      n=ofs&3; if (n) n=4-n;      // dword alignment
  //      ofs=dump(ofs,f, (long)n);
        end=0;
        break;
      } else if (strcmp(name,"MeSsAgEs")==0)    // not sure about this one
      {
        printf("\"\n");
        ofs=dumpl(ofs,f,(max-ofs)/sizeof(long), NULL, "");
        end=0;
        break;
      } else end=1;

    } while (c>=' ' && c<='~' && n<max);
    putchar('\n');
  }
  return ofs;
}

void printheader(int f, FILE *g)
{
  printf("%06lx sig: "); dumptxt(NLMhdr.signature, 24);
  printf("%06lx loader: %08lx  name ", NLMhdr.loader);  printLstr(NLMhdr.name);
  printf("%06lx code:  %08lx  l=%lx\n", NLMhdr.code, NLMhdr.codeLen);
  printf("%06lx data:  %08lx  l=%lx\n", NLMhdr.data, NLMhdr.dataLen);
  printf("%06lx extra data: %lx\n", NLMhdr.unInitLen);
  long custom, customLen;
  long autoLoad, Nautoload;
  long fixup, Nfixup;
  long external, Nexternal;
  long export, Nexport;
  long debug, Ndebug;
  long NLM_load;
  long NLM_unload;
  long NLM_check;
  long type;             // see NLMTypes array
  long unknown;

}

void dumpnlm(int f, FILE *g)
{
  long ofs;
  char name[14];
  long loader;
  ofs=dumpt(0L,f,0x18L);
  ofs=dumpl(ofs,f,1L, &loader, "loader version");
  printf("%05lx : ", ofs);
  read(f,&name,14);  ofs+=14;
  printf("%02x:\"%s\"\n", name[0], name+1);
  ofs=dumpl(ofs,f,2L,nlminfo,"code");
  ofs=dumpl(ofs,f,2L,nlminfo+2,"data");
  ofs=dumpl(ofs,f,1L,nlminfo+4,"          uninitialized data");
  ofs=dumpl(ofs,f,2L,nlminfo+5,"custom data");
  ofs=dumpl(ofs,f,2L,nlminfo+7,"modules");
  ofs=dumpl(ofs,f,2L,nlminfo+9,"relocation table");
  ofs=dumpl(ofs,f,2L,nlminfo+11,"imported symbols");
  ofs=dumpl(ofs,f,2L,nlminfo+13,"exported symbols");
  ofs=dumpl(ofs,f,2L,nlminfo+15,"debug info");
  ofs=dumpl(ofs,f,1L,nlminfo+17,"          load nlm");
  ofs=dumpl(ofs,f,1L,nlminfo+18,"          unload nlm");
  ofs=dumpl(ofs,f,1L,nlminfo+19,"          check nlm");
  ofs=dumpl(ofs,f,1L,nlminfo+20,"type (nlm, lan, dsk, nam)");
  ofs=dumpl(ofs,f,1L,nlminfo+21,"?");
  ofs=dumps(ofs,f,1L);     printf("  description\n");
  ofs=dump(ofs,f,10L);     /* flags ? */
  ofs=dumpt(ofs,f,4L);     /* LONG */
  ofs=dumps(ofs,f,1L);     printf("  screenname\n");
  ofs=dump(ofs, f, 1L);
  ofs=dumps(ofs,f,1L);     printf("  threadname\n");
  ofs=dump(ofs, f, 1L);

/*
   'MeSsAgEs'
   'CoPyRiGhT='  string,  wordalignment
   'VeRsIoN#'    major:long, minor:long, revision:long
*/
  ofs=dumpSpecial(ofs, f, nlminfo[0]);
  inforec.header_start=0;
  inforec.header_end=tell(f)-1;

  if (verbose)
  {
    printf("\n--------------0, 1 code image------------------------------\n");
    printf("   offset %08lx - %08lx\n", nlminfo[0], nlminfo[0]+nlminfo[1]-1);
  }
  inforec.code_start=nlminfo[0];
  inforec.code_end=nlminfo[0]+nlminfo[1]-1;

  if (verbose)
  {
    printf("\n--------------2, 3 data image------------------------------\n");
    printf("   offset %08lx - %08lx\n", nlminfo[2], nlminfo[2]+nlminfo[3]-1);
  }
  inforec.data_start=nlminfo[2];
  inforec.data_end=nlminfo[2]+nlminfo[3]-1;

  if (verbose)
    printf("\n--------------9,10 relocation table -----------------------\n");
  inforec.reloc_start=lseek(f,nlminfo[9],SEEK_SET);
  if (nlminfo[10])
  {
    if (verbose && loader==4) dumpl(nlminfo[9],f,nlminfo[10],NULL,"");
    inforec.reloc_end=tell(f)-1;
  }
  else
    inforec.reloc_end=tell(f);

  if (verbose)
    printf("\n--------------11,12 imported symbols ----------------------\n");
  inforec.import_start=lseek(f,nlminfo[11],SEEK_SET);
  if (nlminfo[12])
  {
    if (verbose && loader==4)
      dumpsn4(nlminfo[11],f,nlminfo[12]);
    inforec.import_end=tell(f)-1;
  }
  else
    inforec.import_end=tell(f);

  if (verbose)
    printf("\n--------------13,14 exported symbols ----------------------\n");
  inforec.exported_code_start=lseek(f,nlminfo[13],SEEK_SET);
  if (nlminfo[14])
  {
    if (verbose && loader==4)
      dumps4(nlminfo[13],f,nlminfo[14]);
    inforec.exported_code_end=tell(f)-1;
  }
  else
    inforec.exported_code_end=tell(f);

  if (verbose)
    printf("\n--------------7,8 needed modules---------------------------\n");
  inforec.modules_start=lseek(f,nlminfo[7],SEEK_SET);
  if (nlminfo[8])
  {
    if (verbose && loader==4)
      dumps(nlminfo[7],f,nlminfo[8]); putchar('\n');
    inforec.modules_end=tell(f)-1;
  }
  else
    inforec.modules_end=tell(f);

  if (verbose)
    printf("\n--------------5,6 custom data------------------------------\n");
  inforec.custom_start=lseek(f,nlminfo[5],SEEK_SET);
  if (nlminfo[6])
  {
    if (verbose && loader==4)
      printf("   offset %08lx - %08lx\n", nlminfo[5], nlminfo[5]+nlminfo[6]-1);
    inforec.custom_end=tell(f)-1;
  }
  else
    inforec.custom_end=tell(f);

  if (verbose)
    printf("\n--------------15,16 debug info-----------------------------\n");
  inforec.debug_start=lseek(f,nlminfo[15],SEEK_SET);
  if (nlminfo[16])
  {
    if (verbose && loader==4)
      dumpb4s(nlminfo[15],f,nlminfo[16]);
    inforec.debug_end=tell(f)-1;
  }
  else
    inforec.debug_end=tell(f);
  inforec.file_end=lseek(f,0,SEEK_END);
}

// returns # bytes still left to copy
long copy(int src, int dst, long len)
{
  char *block;
  block=malloc(BLOCKSIZE);
  while (len>=BLOCKSIZE)
  {
    if (read(src,block,BLOCKSIZE)==-1) {
      break;
    }
    if (write(dst,block,BLOCKSIZE)==-1) {
      break;
    }
    len-=BLOCKSIZE;
  }
  if (len>0 && len<BLOCKSIZE)
  {
    read(src,block,(int)len);    // type cast is ok, len<BLOCKSIZE
    write(dst,block,(int)len);
  }
  free(block);
  return len;
}

// returns filename part of path   (path!=NULL)
char *getfilename(char *path)
{
  static char name[9];
  char *p;
  int len=strlen(path);
  p=path+len;
  while (len>=0 && ! (*p=='/' || *p=='\\'))
  {
    p--;  len--;
  }
  strncpy(name, p, 8);
  p=strchr(name,'.');
  if (p) *p=0;    // remove extension
  return name;
}

char *makefn(char *path, char *ext)
{
  char *p;
  p=malloc(strlen(path)+5);
  strcpy(p,path);
  strcat(p,ext);
  return p;
}

void error(char *s, ...)
{
  va_list ap;
  va_start(ap,s);
  vprintf(s,ap);
  va_end(ap);
  printf("Usage: nlm [-v] <nlmname> [codefile [datafile [customfile [leftover]]]]\n");
  printf("    splits nlm into separate files\n");
  printf("    -v : verbose - print full header info\n");
  printf(" or    nlm -x <nlmname>  [destinationpath]\n");
  printf("    -x : extract all parts of nlm to .cod .dat .cus .txt .lst files\n");
  printf("         in destinationpath\n");

  exit(1);
}

void main(int argc, char **argv)
{
  int f,g;
  char **arg;
  fileid fid;
  int i;
  char c;
  int n;   // # bytes read
  int packedisproblem=0;      // when there is a need to see compressed data

//
//--------------------- process arguments ----------------------
//
  while (--argc)
  {
    arg=*++argv;
    if (*arg=='-')
      switch(*++arg)
      {
        case 'v': verbose++; break;
        case 'x': extractall++; break;
      }
    else
    {
      for (fid=0 ; fid<NR_FILEIDS ; fid++)
        if (outfiles[fid].fn==NULL)
          break;
      if (fid==NR_FILEIDS)
        error("Too many arguments:  %s\n", arg);
      outfiles[fid].fn=arg;
    }
  }

//
//--------------------- check arguments ----------------------
//
  if (outfiles[F_NLM].fn==NULL)
    error("No filename specified\n");
//
// with the -x option
//    F_CODE may optionally contain a destination path
//    F_DATA and up should be empty
//
  if (extractall && outfiles[F_DATA].fn)
    error("Too many parameters for -x option\n");
//
//--------------------- creat filenames ---------------------------------
//
  if (extractall)
  {
    if (outfiles[F_CODE].fn==NULL)
    {
      destpath=malloc(15);   //  './xxxxxxxx.yyy'
      strcpy(destpath,"./");
    }
    else
    {
      pathlen=strlen(outfiles[F_CODE].fn);
      destpath=malloc(pathlen+15);
      strcpy(destpath, code_fn);
      c=outfiles[F_CODE].fn[pathlen-1];
      switch(c)                   // make sure path is in right format
      {
        case ':':                  // only drive
          strcat(destpath, "./");
          break;
        case '\\':                 // ending in dir separator
        case '/':
          break;
        default:   //  append '/'  to path
          strcat(destpath, "/");
      }
    }
    strcat(destpath, getfilename(outfiles[F_NLM].fn));
// now we got everything except for the extensions,
// create filenames for all output files
    for (fid=F_CODE ; fid<=F_LIST ; fid++)
      outfiles[fid].fn=makefn(destpath, outfiles[fid].ext);
  }
//
//--------------------- print to console of no file specified -----------
//  
  if (outfiles[F_LIST].fn==NULL)
    strcpy(outfiles[F_LIST].fn, "CON");   // stdout

  f=open(outfiles[F_NLM].fn,O_BINARY|O_RDONLY);
  if (f<0)
  {
    fprintf(stderr, "source file:"); perror(nlm_fn);
    return;
  }
//
//------------------ process NLM ----------------------------------------
//
  n=read(f, &NLMhdr, sizeof(NLMhdr));
  if (n!=sizeof(NLMhdr))
  {
    fprintf(stderr, "header incomplete:"); perror(outfiles[F_NLM.fn);
    close(f);
    exit(1);
  }
  fg=fopen(outfiles[F_LIST].fn,"w");
  printheader(f,fg);
  fclose(fg);

  for (fid=F_CODE ; fid<F_LIST ; fid++)
  {
    if (outfiles[fid].fn==NULL)
      continue;
    if (*outfiles[fid].start>=NLMhdr.code && NLMhdr.loader&NLM_COMPRESSED)
    {
      packedisproblem++;
      continue;
    }

    g=open(outfiles[fid].fn,O_WRONLY|O_BINARY|O_CREAT|O_TRUNC,S_IWRITE|S_IREAD);
    if (g<0)
    {
      fprintf(stderr, "writing output:"); perror(outfiles[fid].fn);
      continue;
    }

    lseek(f, *outfiles[fid].start, SEEK_SET);
    if (copy(f,g, *outfiles[fid].end- *outfiles[fid].start))
    {
      fprintf(stderr, "copying:"); perror("");
    }
    close(g);
  }
  if (packedisproblem)
    printf("%s is compressed use UNPACK to decompress\n", outfiles[F_NLM.fn);

  close(f);
  if (verbose)
  {
    putchar('\n');
    printf("hdr  : %08lx-%08lx\n", inforec.header_start, inforec.header_end);
    printf("code : %08lx-%08lx\n", inforec.code_start, inforec.code_end);
    printf("data : %08lx-%08lx\n", inforec.data_start, inforec.data_end);
    printf("drel : %08lx-%08lx\n", inforec.reloc_start, inforec.reloc_end);
    printf("crel : %08lx-%08lx\n", inforec.import_start, inforec.import_end);
    printf("exp  : %08lx-%08lx\n", inforec.exported_code_start, inforec.exported_code_end);
    printf("mod  : %08lx-%08lx\n", inforec.modules_start, inforec.modules_end);
    printf("cus  : %08lx-%08lx\n", inforec.custom_start, inforec.custom_end);
    printf("deb  : %08lx-%08lx\n", inforec.debug_start, inforec.debug_end);
    printf("eof  : %08lx\n", inforec.file_end);
  }
}

