#ifdef RCSID
static char RCSid[] =
"$Header: d:/cvsroot/tads/TADS2/msdos/OSDOSEX.C,v 1.2 1999/05/17 02:52:18 MJRoberts Exp $";
#endif

/* Copyright (c) 1997, 2002 Michael J. Roberts.  All Rights Reserved. */
/*
Name
  osdosex.c - external function implementation for DOS
Function
  
Notes
  
Modified
  11/02/97 MJRoberts  - Creation
*/

#ifndef DJGPP
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <share.h>
#endif /* DJGPP */

#include "os.h"
#include <assert.h>

#if defined(__DPMI16__) || defined(__WIN32__)
# include <windows.h>
#endif

/* forward declarations */
static const char *oss_get_exe(const char *argv0);


#ifdef DJGPP

/*
 *   os_exeseek - opens the given .EXE file and seeks to the end of the
 *   executable part of it, on the presumption that a datafile is to be
 *   found there.  
 */
osfildef *os_exeseek(const char *exefile, const char *typ)
{
    return((osfildef *)0);
}

/*
 *   os_exfld - load in an external function from an open file, given the
 *   size of the function (in bytes).  Returns a pointer to the newly
 *   allocated memory block containing the function in memory.  
 */
int (*os_exfld( osfildef *fp, unsigned len ))(void *)
{
    return  (int (*)(void *)) 0;
}

/*
 *   Load an external function from a file.  This routine assumes that the
 *   file has the same name as the resource.  
 */
int (*os_exfil(const char *name))(void *)
{
    return (int (*)(void *)) 0;
}

/*
 *   call an external function, passing it an argument (a string pointer),
 *   and passing back the string pointer returned by the external function 
 */
int os_excall(int (*extfn)(void *), void *arg)
{
    return 0;
}

/* retrieve the full name of the program, given argv[0] */
static const char *oss_get_exe(const char *argv0)
{
    /* 
     *   djgpp reliably passes the fully-qualified path to the executable in
     *   argv[0], so we can simply return argv[0] 
     */
    return argv0;
}

#else /* DJGPP */

/*
 *   os_exeseek - opens the given .EXE file and seeks to the end of the
 *   executable part of it, on the presumption that a datafile is to be
 *   found there.  
 */
osfildef *os_exeseek(const char *argv0, const char *typ)
{
    const char *exefile;
    osfildef *fp;
    int       tags_found;
    unsigned  ofs, segcnt;
    unsigned  long seekpt;
    unsigned  long check;
    unsigned  long startofs;
    unsigned  long end;
    char      typbuf[4];
    static    int no_data_present = FALSE;
    static    int tried_old_way = FALSE;

    /* 
     *   if we've already looked here and found no data, don't bother trying
     *   again 
     */
    if (no_data_present)
        return 0;

    /* get the name of the executable file from the given argv0 */
    exefile = oss_get_exe(argv0);

    /* open the file */
    if (!(fp = _fsopen(exefile, "rb", SH_DENYWR)))
        return((FILE *)0);

    /* seek to the end of the file */
    fseek(fp, 0L, SEEK_END);

    /* look through tagged blocks until we find the type we want */
    for (tags_found = 0 ; ; ++tags_found)
    {
        /* seek back past the descriptor block */
        fseek(fp, -12L, SEEK_CUR);
        seekpt = ftell(fp);

        /* read the elements of the descriptor block */
        if (fread(&check, sizeof(check), 1, fp) != 1
            || fread(typbuf, sizeof(typbuf), 1, fp) != 1
            || fread(&startofs, sizeof(startofs), 1, fp) != 1)
            break;

        /* check the signature to make sure we're looking at a valid block */
        if (check != ~seekpt)
            break;

        /* seek to the start of the data for this resource */
        fseek(fp, startofs, SEEK_SET);

        /* if this is the one we want, return it, otherwise keep looking */
        if (!memcmp(typ, typbuf, sizeof(typbuf)))
        {
            /* check the header to make sure it matches */
            if (fread(&check, sizeof(check), 1, fp) != 1
                || fread(typbuf, sizeof(typbuf), 1, fp) != 1
                || check != ~startofs
                || memcmp(typ, typbuf, sizeof(typbuf)))
                break;
            return fp;
        }
    }

    /*
     *   We didn't find it - if the caller is asking for the game file
     *   segment ("TGAM"), try the old way, in case this got built with the
     *   old maketrx program.  If we've already tried this before in this
     *   session, don't bother doing so again, since nothing's going to
     *   change.  
     */
    if (!tried_old_way)
    {
        /* note that we've tried this now */
        tried_old_way = TRUE;

        /* read the header */
        fseek(fp, 0L, SEEK_SET);
        if (fread(&ofs, sizeof(ofs), 1, fp) != 1 ||
            fread(&ofs, sizeof(ofs), 1, fp) != 1 ||
            fread(&segcnt, sizeof(segcnt), 1, fp) != 1)
            goto no_data;
        
        seekpt = ((unsigned long)segcnt - 1)*512L + (unsigned long)ofs;
        if (fseek(fp, seekpt, SEEK_SET)) goto no_data;
        
        /* check for the signature, to make sure it's not the symbol table */
        if (fread(&end, sizeof(end), 1, fp) != 1
            || fseek(fp, 0L, SEEK_END)
            || (unsigned long)ftell(fp) != end
            || fseek(fp, seekpt + sizeof(end), SEEK_SET))
            goto no_data;
        
        /* success! */
        return(fp);
    }

no_data:
    /* 
     *   if we didn't find any tags at all, note that there's no data
     *   present, so we don't try reading fruitlessly again 
     */
    if (tags_found == 0)
        no_data_present = TRUE;

    /* we didn't find anything - close the file and return failure */
    fclose(fp);
    return((FILE *)0);
}

/* ------------------------------------------------------------------------ */
/*
 *   Real-mode and 16-bit protected mode external function interfaces 
 */

#ifndef __WIN32__

static void *canon(void *ptr)
{
    unsigned long p = (unsigned long)ptr;
    unsigned long abs = ((p >> 16) << 4) + (p & 0xffff);
    if (abs & (unsigned long)0xf)
        abs = (abs & ~(unsigned long)0xf) + (unsigned long)0x10;
    return((void *)(abs << 12));
}

/*
 *   os_exfld - load in an external function from an open file, given the
 *   size of the function (in bytes).  Returns a pointer to the newly
 *   allocated memory block containing the function in memory.  
 */
int (*os_exfld(osfildef *fp, unsigned len))(void *)
{
    void      *extfn;
    unsigned   alo;

#ifdef MSOS2
    /* for OS/2, don't load anything, but seek past the resource */
# ifdef MICROSOFT
    if (_osmode == OS2_MODE)
# endif /* MICROSOFT */
    {
        osfseek(fp, (long)len, OSFSK_SET);
        return((int (*)(void))0);
    }
#endif /* MSOS2 */

#ifdef __DPMI16__

    HANDLE selector;

    selector = GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, len);
    if (!selector || !(extfn = GlobalLock(selector)))
        return 0;

    /* read the file */
    osfrb(fp, extfn, len);

    /* change the selector to a code segment */
    _asm
    {
        mov   bx, selector                                  /* get selector */
            lar   ax, bx                       /* get current access rights */
            mov   cl, ah
            or    cl, 8               /* set the CODE bit in the descriptor */
            mov   ax, 9                 /* function = set descriptor rights */
            mov   ch, 0
            int   31h
    }

    /* close the file and return the pointer to the function in memory */
    return((int (*)(void *))extfn);

#else /* __DPMI16 __ */

    /* figure out how much memory is needed and allocate it */
    alo = ((len + 0xf) & ~0xf) + 16;    /* round to mult of 16, plus 1 page */
    extfn = canon(malloc(alo));/* allocate the memory, canonicalize pointer */
    if (!extfn)
        return((int (*)(void *))0 );

    /* read the file */
    osfrb(fp, extfn, len);

    /* close the file and return the pointer to the function in memory */
    return((int (*)(void *))extfn);

#endif /* __DPMI16__ */
}

/*
 *   Load an external function from a file.  This routine assumes that the
 *   file has the same name as the resource. 
 */
int (*os_exfil(const char *name))(void *)
{
    FILE      *fp;
    unsigned   len;
    int      (*extfn)(void *);

#ifdef MSOS2
    /* load the function from a DLL of the same name as the function */
# ifdef MICROSOFT
    if (_osmode == OS2_MODE)
# endif /* MICROSOFT */
    {
        CHAR    failname[128];
        HMODULE hmod;
        PFN     pfn;

        if (DosLoadModule(failname, sizeof(failname), (PSZ)name, &hmod)
            || DosGetProcAddr(hmod, (PSZ)"_main", &pfn))
        {
            return((int (*)(void))0);
        }
        return((int (*)(void))pfn);
    }
#endif /* MSOS2 */

#ifdef __DPMI16__

#endif

    /* open the file and see how big it is to determine our memory needs */
    if ( !( fp = fopen( name, "rb" ))) return( (int (*)(void *))0 );
    (void)fseek( fp, 0L, 2 );
    len = (unsigned)ftell( fp );                    /* total length of file */

    (void)fseek( fp, 0L, 0 );
    extfn = os_exfld(fp, len);

    fclose( fp );
    return( extfn );
}

/*
 *   call an external function, passing it an argument (a string pointer),
 *   and passing back the string pointer returned by the external function
 */
int os_excall(int (*extfn)(void *), void *arg)
{
    return((*extfn)(arg));
}

#endif /* !__WIN32__ */

/* ------------------------------------------------------------------------ */
/*
 *   Win32 external function interfaces - not implemented at present 
 */

#ifdef __WIN32__

int (*os_exfld(osfildef *fp, unsigned len))(void *)
{
    /* NOT IMPLEMENTED - scan past the resource and fail */
    osfseek(fp, (long)len, OSFSK_CUR);
    return 0;
}

int (*os_exfil(const char *name))(void *)
{
    HINSTANCE hlib;
    FARPROC proc;
    
    /* 
     *   load the library of the given name; if we can't load the library,
     *   we can't load the external function 
     */
    hlib = LoadLibrary(name);
    if (hlib == 0)
        return 0;

    /* get the address of the "_main" procedure */
    proc = GetProcAddress(hlib, "main");

    /* if that failed, unload the library and return failure */
    if (proc == 0)
    {
        FreeLibrary(hlib);
        return 0;
    }

    /* 
     *   return the procedure address, suitably cast; unfortunately, we
     *   have no provision for freeing the library instance handle, so
     *   we'll just have to count on Windows releasing it when the process
     *   terminates 
     */
    return (int (*)(void *))proc;
}

int os_excall(int (*extfn)(void *), void *arg)
{
    /* call the function directly */
    return((*extfn)(arg));
}

#endif /* __WIN32__ */

/* ------------------------------------------------------------------------ */
#ifdef MICROSOFT

/* 
 *   retrieve the full name of the program, given argv[0] 
 */
static const char *oss_get_exe(const char *argv0)
{
    /*
     *   The MSVC run-time library keeps the full filename of the executable
     *   in the global variable '_pgmptr'.  
     */
    return _pgmptr;
}

#else

/* 
 *   retrieve the full name of the program, given argv[0] 
 */
static const char *oss_get_exe(const char *argv0)
{
    /*
     *   for non-microsoft compilers, we'll have to rely on the run-time
     *   library to pass the fully-qualified path to the executable in argv0 
     */
    return argv0;
}

#endif /* MICROSOFT */

#endif /* DJGPP */

/* ------------------------------------------------------------------------ */
/*
 *   Get the executable filename given the main program's argv[0].  On DOS
 *   and Windows, argv[0] always contains the full path to the executable
 *   file, regardless of what the user typed on the command line - the shell
 *   always expands relative names to absolute paths, and inserts the PATH
 *   variable expansion as needed.  So, we simply copy the argv[0] value
 *   into the caller's buffer.  
 */
int os_get_exe_filename(char *buf, size_t buflen, const char *argv0)
{
    /*
     *   If the name string is too long, fail.  Note that the name is too
     *   long if its strlen is so much as equal to the buffer length,
     *   because we need one extra byte for a null terminator in the copied
     *   result.  
     */
    if (strlen(oss_get_exe(argv0)) >= buflen)
        return FALSE;

    /* copy it and return success */
    strcpy(buf, oss_get_exe(argv0));
    return TRUE;
}

/*
 *   Get a special path.  
 */
void os_get_special_path(char *buf, size_t buflen, const char *argv0, int id)
{
    char tmp[OSFNMAX];

    /* determine which path we're retrieving */
    switch(id)
    {
    case OS_GSP_T3_RES:
        /* 
         *   For DOS/Windows, we keep all TADS 3 system resources in the
         *   root install directory, which is the same directory that
         *   contains the executable.  Simply get the path name of the
         *   executable.  
         */
        os_get_path_name(buf, buflen, oss_get_exe(argv0));

        /* 
         *   canonicalize the path by converting it to all lower-case (since
         *   DOS/Win file systems are not sensitive to case, this won't
         *   change the meaning of the filename, but it will ensure that it's
         *   stable for comparisons to the same path obtained by other means)
         */
        strlwr(buf);
        return;

    case OS_GSP_T3_LIB:
        /*
         *   For DOS/Windows, we keep TADS 3 library source files in the
         *   "lib" subdirectory of the install directory.  
         */
        os_get_path_name(tmp, sizeof(tmp), oss_get_exe(argv0));
        os_build_full_path(buf, buflen, tmp, "lib");

        /* return in all lower-case for consistency */
        strlwr(buf);
        return;

    case OS_GSP_T3_INC:
        /*
         *   For DOS/Windows, we keep TADS 3 library header files in the
         *   "include" subdirectory of the install directory.  
         */
        os_get_path_name(tmp, sizeof(tmp), oss_get_exe(argv0));
        os_build_full_path(buf, buflen, tmp, "include");

        /* return in all lower-case for consistency */
        strlwr(buf);
        return;

    case OS_GSP_T3_USER_LIBS:
        /*
         *   For DOS/Windows, the user library path is taken from the
         *   environment variable TADSLIB.  If this environment variable
         *   isn't set, there's no default value.  
         */
        {
            char *p;

            /* try getting the environment variable */
            p = getenv("TADSLIB");

            /* if it exists, set it; otherwise, return an empty string */
            if (p != 0)
            {
                size_t copy_len;

                /* copy as much as we can fit in the buffer */
                copy_len = strlen(p);
                if (copy_len > buflen - 1)
                    copy_len = buflen - 1;

                /* 
                 *   copy the (possibly truncated) string, and null terminate
                 *   the result 
                 */
                memcpy(buf, p, copy_len);
                buf[copy_len] = '\0';
            }
            else
            {
                /* the variable isn't set - return empty */
                buf[0] = '\0';
            }
        }
        return;

    default:
        /*
         *   If we're called with another identifier, it must mean that
         *   we're out of date.  Fail with an assertion. 
         */
        assert(FALSE);
        break;
    }
}
