/***************************************************************************
*       NAME:  PROFILE.C $Revision: 1.6 $
**      COPYRIGHT:
**      "Copyright (c) 1994, by FORTE
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: stuff.c $
* Revision 1.6  1995/06/05 10:48:47  VFERRER
* Added matching file_close
* Revision 1.5  1995/06/01 01:05:43  VFERRER
* Added MLS support
* Revision 1.4  1995/05/05 07:28:32  MTRAVERS
* Removed unneeded code in find_rom_sbos
* Revision 1.3  1995/05/03 10:56:36  MTRAVERS
* Made changes for rom and rational bug fix
* Revision 1.2  1994/04/26 15:41:30  VFERRER
* profile file open is now read only
* Revision 1.1  1995/02/24 14:44:12  unknown
* Initial revision
* Revision 1.1  1994/11/30 09:02:06  unknown
* Initial Revision
* Revision 1.1  1994/09/12 15:17:25  unknown
***************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>
#include "stuff.h"
#include "findsbos.h"

#include "sbosdata.h"
#include "sbosdefs.h"
#include "shared.h"
#include "iwpatch.h"
#include "error.h"

extern char mls_path[];

/* multiple language support */
/* MLS files contain:
**     unsigned long num_messages;
**     struct IW_MESSAGE_INFO info[];   //an array of message info structs 
**     text
*/
struct IW_MESSAGE_INFO {
    unsigned short message_id;
    unsigned short size;
    unsigned long message_offset;
};

/***************************************************************************
OVERVIEW:
profile.c - profile utilities

This file contains utility routines for use by wrappers or applications.
The utilities are for reading data from a .ini file in the standard
Microsoft Windows format:

[section1]
entry1=value1
entry2=value2

[section2]
.
.
.

*/

PeekString(unsigned int port, unsigned long location,
           void far *data,unsigned int length,int rom_flag);

static char input_line[256];

static char *iw="INTERWAVE";
static char RFAR *ustr;
static char buf[16];
static char RFAR *parse_str=0;
static unsigned short iwl_status;

#if NEARDATA == 1
#define ENV_STR_SIZE 132
static char env_str[ENV_STR_SIZE];
#endif

#define UPPER(a) (((a)>='a' && (a)<='z')?((a)-'a'+'A'):(a))

/* ctype.h */
/***************************************************************************

FUNCTION DEFINITION:
iwu_isspace - tests whether the character c is a whitespace character

RETURNS: 1 if c is whitespace 0 if not

*/
int iwu_isspace(int c)
{
  if (c >= 0x09 && c <= 0x0D)
    return 1;
  return (c == ' ');
}

/* string.h */
/***************************************************************************

FUNCTION DEFINITION:
iwu_strcat - appends string src to string dest

RETURNS: char RFAR * - pointer to the concatinated strings

*/
char RFAR *iwu_strcat(char RFAR *dest, const char RFAR *src)
{
  char RFAR *d = dest;
  while (*d)
    d++;
  //while (*d++ = *src++)
  while(*src != NULL)
     *d++ = *src++;
  *d = 0;
  return dest;
}

/***************************************************************************

FUNCTION DEFINITION:
iwu_strlen - calculates the length of string s1

RETURNS: int - number of characters in s1 not including the null
               terminating character.

*/
int iwu_strlen(const char RFAR *s1)
{
  int i = 0;
  while (*s1++) i++;
  return i;
}

/***************************************************************************

FUNCTION DEFINITION:
iwu_stricmp - compares the contents of strings s1 and s2 (case insensitive)

RETURNS: int - < 0 if s1 < s2
               = 0 if s1 = s2
               > 0 if s1 > s2

*/
int iwu_stricmp(const char RFAR *s1, const char RFAR *s2)
{
  while (*s1 && UPPER(*s1) == UPPER(*s2)) {
    s1++;
    s2++;
  }
  return *s1 - *s2;
}

/***************************************************************************

FUNCTION DEFINITION:
iwu_strcpy - copies the characters from src to dest

RETURNS: char RFAR * - dest

*/
char RFAR *iwu_strcpy(char RFAR *dest, const char RFAR *src)
{
  char RFAR *d = dest;
  while (*src)
        *d++ = *src++;
  *d = 0;
  return dest;
}

/***************************************************************************

FUNCTION DEFINITION:
iwu_strncmp - compares a portion of one string to another

DESCRIPTION:
iwu_strncmp comapares at most maxlen characters between s1 and s2.

RETURNS: int - < 0 if s1 < s2
               = 0 if s1 = s2
               > 0 if s1 > s2

*/
int iwu_strncmp(const char RFAR *s1, const char RFAR *s2, unsigned int maxlen)
{
  int i = 0;
  while (i < (int)maxlen && *s1 && *s1 == *s2) {
    i++;
    s1++;
    s2++;
  }
  return (i == (int)maxlen) ? 0 : (*s1 - *s2);
}

/***************************************************************************

FUNCTION DEFINITION:
iwu_strncpy - copies a portion of one string to another

DESCRIPTION:
iwu_strncpy copies at most maxlen characters from the string src to the string
dest.  dest is null-padded or truncated if the length of src is less than
maxlen.  dest may not be null-terminated if the length of src is maxlen or
more.

RETURNS: char RFAR * - dest

*/
char RFAR *iwu_strncpy(char RFAR *dest, const char RFAR *src, unsigned int maxlen)
{
  int i = 0;
  char RFAR *d = dest;
  while (i < (int)maxlen)
        {
        *d++ = *src++;
    i++;
        }
if (i< maxlen)
        *d= 0;
  return dest;
}

void skip_whitespace(char RFAR *cp, char RFAR *RFAR*pcp)
{
    while (iwu_isspace(*cp)) cp++;
    *pcp = cp;
}

/***************************************************************************

FUNCTION DEFINITION:
iwu_strtok - search a string for tokens

DESCRIPTION:
iwu_strtok searches a string, str, for tokens separated by the characters in
the delimiter string.  iwu_strtok considers str to consist of a sequence of 
zero or more text tokens separated by spans of one or more characters from
delimiter.

The first call to iwu_strtok returns a pointer to the first character of 
the first token of str and writes a null character into str immediately
followint the returned token.  Subsequent calls with NULL for the first
argument will work through the string str in this fassion until no tokens
remain.

The delimiter string can be different from call to call.

RETURNS: char RFAR * - a pointer to the first token in str.
         NULL when there are no more tokens

*/
char RFAR *iwu_strtok(char RFAR *str, const char RFAR *delimiter)
{
  char RFAR *tmp;
  char RFAR *start;

  if (str == 0) {
    if (parse_str == 0)
      return(0);
    else
      tmp = parse_str;
  }
  else {
    tmp = str;
  }
  start = tmp;
  if (*tmp) {
    while (*tmp && iwu_strncmp(tmp, delimiter, iwu_strlen(delimiter)))
      tmp++;
    if (*tmp != '\0')
      *tmp++ = '\0';
    parse_str = tmp;
  }
  else start = 0;
  return(start);
}

/***************************************************************************

FUNCTION DEFINITION:
iwu_strtol - convert a string to a long integer

DESCRIPTION:
iwu_strtol converts a character string, str, to a long integer value.  This
function replaces the standard C runtime strtol function.

RETURNS: long - value of converted string or 0 on error

SEE ALSO: 
Borland Run-Time Library or Microsoft Run-Time library reference for a 
description of strtol.

*/
long iwu_strtol(const char RFAR *str, char RFAR * RFAR *endptr, int rad)
{
  int radix;
  long rval = 0;
  int neg = 0;

  if (rad >= 2 && rad <= 36) {
    radix = rad;
    if (radix == 10 && str[0] == '-') {
      neg = 1;
      str++;
    }
  }
  else if (rad == 0) {
    if (str[0] == '0') {
      if (str[1] == 'x') {
    radix = 16;
    str = &str[2];
      }
      else {
    radix = 8;
    str = &str[1];
      }
    }
    else {
      radix = 10;
      if (str[0] == '-') {
    neg = 1;
    str++;
      }
    }
  }
  else {
    if (endptr) *endptr = (char RFAR *)str;
    return(0L);
  }

  while (1) {
    if (*str >= 0x30 && *str <= 0x39 && *str < (0x30 + radix)) {
      rval = rval * radix + (*str - 0x30);
    }
    else if (*str >= 0x41 && *str < (0x37 + radix)) {
      rval = rval * radix + (*str - 0x37);
    }
    else if (*str >= 0x61 && *str < (0x57 + radix)) {
      rval = rval * radix + (*str - 0x57);
    }
    else {
      break;
    }
    str++;
  }
  if (endptr) *endptr = (char RFAR *)str;
  if (neg) rval = -rval;
  return(rval);
}

/***************************************************************************

FUNCTION DEFINITION:
file_read - read a number of bytes from a file

file_read is a substitue for the C runtime library function "read".  This
routine reads up to 64K bytes from a file into a buffer specified by
the caller.

RETURNS: USHORT - number of bytes actually read
                  0 if at EOF before call
*/                  
USHORT file_read(
  int handle,           /* file handle to read from */
  void far *io_buffer,  /* buffer to hold data from file */
  USHORT size)          /* number of bytes to read */
{
        _CX = size;
        _DX = FP_OFF(io_buffer);
        _BX = handle;
asm     push    ds;
        _DS = FP_SEG(io_buffer);
        _AX = DOS_READ;
        geninterrupt(DOS_TRAP);
asm     pop     ds
        return(_AX);
}

/***************************************************************************

FUNCTION DEFINITION:
file_gets - get a string from a file

file_gets is a substitute for the standard C runtime fgets function.
This routine reads a string of characters from a file a places it into
a buffer specified by the caller.  The caller the maximum number of
bytes to be read.  This function stops when it reads either the maximum
number - 1 or a newline.

RETURNS: char far * - pointer to the string read in
                      NULL on EOF or error
*/
char far *file_gets(
  int handle,           /* file to read data from */
  char far *io_buffer,  /* buffer for data from file */
  unsigned short size)  /* maximum number of bytes to be read */
{
        USHORT actual;
        long offset;

        /* Read the maximum possible buffer size */
        actual = file_read(handle, io_buffer, size-1);
        if (actual == 0) return NULL;
        /* Find the newline (in relation to the end of the string) */
        io_buffer[actual] = '\0';
        for (offset = 0; offset < (long)actual; offset++) {
            if (io_buffer[offset] == '\n') {
                io_buffer[++offset] = '\0';
                break;
            }
            if (io_buffer[offset] == '\r')
             io_buffer[offset] = '\n';
        }
        offset -= (long)actual;
        /* Backtrack the file pointer to point to the newline */
        file_seek(handle, offset, DOS_SEEK_CURRENT);

        return(io_buffer);
}

/***************************************************************************

FUNCTION DEFINITION:
file_close - close a file

file_close is a substitute for the standard C runtime function: fclose.

RETURNS: USHORT - 0 on success
                  EOF if any errors were detected
*/
USHORT file_close(
  int handle) /* file handle to close */
{
        _BX = handle;
        _AX = DOS_CLOSE;
        geninterrupt(DOS_TRAP);
        if (_FLAGS & CARRY_FLAG) {
            iwl_status = _AX;
            return(IW_DOS_ERROR);
        }
        return(IW_OK);
}

/***************************************************************************

FUNCTION DEFINITION:
file_seek - reposition a file pointer

file_seek is a substitute for the standard C runtime function: fseek.
Positions the file pointer a number of bytes from the file location given
by the method paremeter.

The method parameter can be one of:
  DOS_SEEK_START   - offset from beginning of file
  DOS_SEEK_END     - offset from the end of the file
  DOS_SEEK_CURRENT - offset from the current position of the file

RETURNS: USHORT - 0 on success
                  non-zero on error
*/
USHORT file_seek(
  int handle,   /* file to reposition pointer */
  ULONG offset, /* number of bytes to change pointer position */
  int method)   /* file position to apply offset to */
{
        _CX = (ULONG)(offset >> 16);
        _DX = (ULONG)offset;
        _BX = handle;
        _AX = DOS_SEEK + method;
        geninterrupt(DOS_TRAP);
        if (_FLAGS & CARRY_FLAG) {
            iwl_status = _AX;
            return(IW_DOS_ERROR);
        } else {
            _BX = _AX;
        }
        return(IW_OK);
}

/***************************************************************************

FUNCTION DEFINITION:
file_open - open a file for reading and writing

file_open is a substitute for the standard C runtime function: fopen.
This routine opens the file specified for both reading and writing.

RETURNS: int - pointer to opened file
               NULL on error
*/
int file_open(
  char far *name) /* path including file name to be opened */
{
        _DX = FP_OFF(name);
asm     push    ds;
        _DS = FP_SEG(name);
        _AX = DOS_OPEN_R;
        geninterrupt(DOS_TRAP);
asm     pop     ds;
        if (_FLAGS & CARRY_FLAG) asm neg ax
        return(_AX);
}

/***************************************************************************

FUNCTION DEFINITION:
search_disk_profile - search a profile on disk

DESCRIPTION:
This routine opens a profile on disk and searches the contents for the
section, entry, and value specified by the caller.  The profile is
expected to be in the standard Microsoft Windows .ini file format 
described in the OVERVIEW.

The general flow of the routine is:
loop1: get a line from the file
 - if the first char is a '[' then this is a new section
   - compare the section name with the specified name
   - if equal
     - loop2: get the next line
     - if the first character is alphanumeric
        - find the '='
        - compare the string up to the '=' to the specified entry
        - if equal
          - copy the characters following the '=' to the caller's buffer
          else goto loop2
     else goto loop1

RETURNS: int - number of bytes copied to the ouput buffer

SEE ALSO: 
GetPrivateProfileString in the Microsoft Windows SDK documentation

*/
static int search_disk_profile(
  char RFAR *Section,   /* section to search for */
  char RFAR *Entry,     /* entry to search for */
  char RFAR *Filename,  /* file name of profile to search */
  char RFAR *Buffer,    /* caller's data buffer to copy value into */
  int   BufSize)        /* size of data buffer */
{
  int fp;
  char RFAR *buffer, RFAR *val, RFAR *il;
  int input_lineok, error = 0, found_section=0;
  unsigned int i;
  int filled;
  unsigned int limit;

  filled = 0;
  fp = file_open(Filename);
  if (fp >= 0) {
    file_seek(fp, 0, DOS_SEEK_START);
    while (!error) {
      if (file_gets(fp, (char RFAR *)input_line, 255)) {
        skip_whitespace(input_line, &il);
        if (*il == '[') {
          if (found_section && Entry == 0) {
            error = FOUND_ENTRY;
          } else {
            for (i=0;i<(UINT)iwu_strlen(il);i++)
              if (il[i] == ']') il[i]='\0';
            if (iwu_stricmp(Section, (char RFAR *)&il[1]) == 0) {
              found_section = 1;
              buffer = Buffer;
              limit = BufSize;
            }
            else found_section = 0;
          }
        } else {
          if (found_section) {
            input_lineok = 0;
            if (il[0] != ';') {
              for (i=0;i<(UINT)iwu_strlen(il);i++) {
                if (il[i] == '=') {
                  input_lineok = 1;
                  il[i] = 0;
                  val = (char RFAR *)&il[i+1];
                  for (i=0;i<(UINT)iwu_strlen(val);i++)
                    if (val[i] == '\n') {
                      val[i] = 0;
                      break;
                    }
                  break;
                }
              }
            }
            if (input_lineok) {
              if (Entry) {
                if (iwu_stricmp(Entry,
                               (char RFAR *)il) == 0) {
                  iwu_strncpy(buffer, val, BufSize);
                  buffer[BufSize - 1] = '\0';
                  filled += iwu_strlen(buffer);
                  error = FOUND_ENTRY;
                }
              } else {
                if ((UINT)iwu_strlen(il) < limit) {
                  iwu_strcpy(buffer, (char RFAR *)il);
                  buffer += iwu_strlen((char RFAR *)il) + 1;
                  limit -= (iwu_strlen((char RFAR *)il) + 1);
                  filled += iwu_strlen((char RFAR *)il) + 1;
                  buffer[0] = '\0';
                  buffer[1] = '\0';
                }
                else {
                  if (limit > 0) iwu_strncpy(buffer, 
                                            (char RFAR *)il,
                                            limit);
                  filled = BufSize - 1;
                  Buffer[BufSize - 2] = '\0';
                  Buffer[BufSize - 1] = '\0';
                  error = BUFFER_FULL;
                }
              }
            }
          }
        }
      }
      else error = END_OF_FILE;
    }
    file_close(fp);
  }
  else error = NO_SUCH_FILE;

  return(filled);
}

/***************************************************************************

FUNCTION DEFINITION:
iwu_get_profile_string - get profile string

DESCRIPTION:
iwu_get_profile_string performs the same function as the Microsoft
Windows function GetPrivateProfileString.

This routine searches the in memory list of profiles that may be cached
with a call to iwu_load_profile.  If there are no cached profiles in
memory, the profile is searched on disk.

If this routine is being compiled specifically for use with Microsoft
Windows, the actual Microsoft GetPrivateProfileString is called.

RETURNS: int - number of bytes copied to the ouput buffer

SEE ALSO: 
iwu_load_profile
GetPrivateProfileString in the Microsoft Windows SDK documentation

*/
int iwu_get_profile_string(
  char RFAR *section,     /* section to search for */
  char RFAR *entry,       /* entry to search for */
  char RFAR *default_str, /* default value if entry does not exist */
  char RFAR *buffer,      /* caller's data buffer to copy value into */
  int   buf_size,         /* size of data buffer */
  char RFAR *file)        /* file name of profile to search */
{
  int rc;
    /* Go to disk */
  rc = search_disk_profile(section, entry, file, buffer, buf_size);

  if (!rc) {
    if (default_str == 0) default_str = "";
    iwu_strncpy(buffer, default_str, buf_size);
    rc = iwu_strlen(buffer);
  }
  return(rc);
}

/****************************************************************************

FUNCTION DEFINITION:
find_sbos -

DESCRIPTION:
        - will detect using SBOS stamp and 7x vector if SBOS is currently
            loaded.  Will return 0 if Not loaded and the vector if loaded.
        - will compile and work under large and small models

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
unsigned int find_sbos( void )
{
unsigned int    code_seg, i, vector;    
unsigned char far *stamp_str;

for( vector = MIN_SBOS; vector <= MAX_SBOS; vector++ )
    {
    code_seg = FP_SEG( getvect( vector ) );
    stamp_str = (unsigned char far *)MK_FP( code_seg, STAMP_OFFSET );
    if( iwu_strncmp( (unsigned char far *)stamp_str, "SBOS", 4 ) == 0x0 )
        {
        return( vector );
        }
    }
return( 0 );
}

/****************************************************************************

FUNCTION DEFINITION:
find_rom_sbos -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
long find_rom_sbos(int port, SBOS_LIB_HEADER *lib_header,SHARED *shared,int rom_flag)
{
long rom_addr;
long rom_place;
long temp;
long wave_loc=0L;
ROM_HDR rom_hdr;
CHUNK_HDR fff_chunk;
CHUNK_HDR sbos_chunk;
ROMSBOS romsbos;
int i;
int got_first;

got_first = FALSE;

for (i=0;i<4;i++)
    {
    // See if this rom slot has the SBOS chunk in it.
    rom_place = rom_addr = (long)i*1024L*1024L*4L;
    // Read this ROM Header.
    PeekString(port, rom_place, &rom_hdr, sizeof(rom_hdr),rom_flag);
    // See if its a valid INTRWAVE header ...
    if (iwu_strncmp(rom_hdr.iwave,"INTRWAVE",8) == 0)
        {
        rom_place += sizeof(rom_hdr);
        // Save the base location for this ROM chip ....
        if (rom_hdr.series_number == 0)
            {
            if (!got_first)
                {
                // OK. I go the FIRST rom with series #0 ...
                shared->rom_addr[rom_hdr.series_number] = rom_addr;
                got_first = TRUE;
                // Now shuffle thru FFF file stuff to find sbos chunk
                while(TRUE)
                    {
                    PeekString(port, rom_place, &fff_chunk, sizeof(fff_chunk),rom_flag);
                    if (iwu_strncmp(fff_chunk.tag,"FFFF",4) == 0)
                        {
                        PeekString(port, rom_place+sizeof(fff_chunk), &sbos_chunk, sizeof(fff_chunk),rom_flag);
                        if (iwu_strncmp(sbos_chunk.tag,"SBOS",4) == 0)
                            {
                            rom_place +=  2*sizeof(fff_chunk);
                            PeekString(port, rom_place, &romsbos, sizeof(romsbos),rom_flag);
                            // Found it. Now fixup some stuff ....
                            // First fill in library header.
                            *lib_header = romsbos.sboslib;
        temp = rom_place+sizeof(SBOS_LIB_HEADER);
        lib_header->dram_info.dram_begin = temp;
        temp += 32L;
        lib_header->dram_info.dram_stopped_voice = temp;
        temp += 32L;
        lib_header->dram_info.dram_temp_buff = temp;
        temp += (32L*2L);
        lib_header->dram_info.dram_perc_map = temp;
                            rom_place +=  sizeof(ROMSBOS);
                            wave_loc = ((rom_place + 31L) & -32L);
        temp = wave_loc + (lib_header->num_waves*sizeof(WAVE_DATA));
        temp = ((temp + 31L) & -32L);
        lib_header->dram_info.dram_wave_info = temp;
        temp = temp + (lib_header->num_waves*sizeof(WAVE_INFO));
        temp = ((temp + 31L) & -32L);
        lib_header->dram_info.dram_mpu_info = temp;
                            // rom place is where variable length data is ....
                            break;
                            }
                        else    // skip past this FFFF to the NEXT one
                            rom_place += (sizeof(fff_chunk)+fff_chunk.length);
                        }
                    else    // No more FFFF's .....
                        break;
                    }
                }
            }
        else
            {
            shared->rom_addr[rom_hdr.series_number] = rom_addr;
            }
        }
    }
if (!got_first)
    return(0);
else
    return(wave_loc);
}

/****************************************************************************

FUNCTION DEFINITION:
iw_mls_init -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
void iw_mls_init(char RFAR *path)
{
    iwu_strcpy(mls_path, path);
}

/****************************************************************************

FUNCTION DEFINITION:
iw_mls_strlen -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
long iw_mls_strlen(char RFAR *langfile, unsigned short id)
{
    char fname[128];
    int fp;
    unsigned long nmessages;
    struct IW_MESSAGE_INFO mi;
    
    iwu_strcpy(fname, mls_path);
    iwu_strcat(fname, "\\");
    iwu_strcat(fname, langfile);
    fp = file_open(fname);
    if (fp < 0)
        return(-1);

    if (file_read(fp, &nmessages, sizeof(unsigned long)) != 
        sizeof(unsigned long)) 
        {
        file_close(fp);
        return(-1);
        }

    while (nmessages) 
        {
        if (file_read(fp, &mi, sizeof(struct IW_MESSAGE_INFO)) != 
            sizeof(struct IW_MESSAGE_INFO)) 
            {
            file_close(fp);
            return(-1);
            }
        if (mi.message_id == id) break;
        nmessages--;
        }

    file_close(fp);
    if (nmessages == 0) return(-1);
    return(mi.size);
}

/****************************************************************************

FUNCTION DEFINITION:
iw_mls_getstring -

DESCRIPTION:

EXPECTS:

MODIFIES:

SEE ALSO:

RETURNS:

*/
int iw_mls_getstring(char RFAR *langfile, unsigned short index, char RFAR *buf, unsigned short buflen)
{
    char fname[128];
    int fp;
    unsigned long nmessages;
    struct IW_MESSAGE_INFO mi;
    
    iwu_strcpy(fname, mls_path);
    iwu_strcat(fname, "\\");
    iwu_strcat(fname, langfile);
    fp = file_open(fname);
    if (fp < 0)
        return(ERROR_BAD_MESSAGE_FILE);

    if (file_read(fp, &nmessages, sizeof(unsigned long)) != 
        sizeof(unsigned long)) 
        {
        bad_read:
        file_close(fp);
        return(ERROR_BAD_MESSAGE_FILE);
        }

    while (nmessages) 
        {
        if (file_read(fp, &mi, sizeof(struct IW_MESSAGE_INFO)) 
            != sizeof(struct IW_MESSAGE_INFO)) 
            goto bad_read;

        if (mi.message_id == index) break;
        nmessages--;
        }

    if (nmessages == 0) 
        {
        bad_message:
        file_close(fp);
        return(ERROR_BAD_MESSAGE_FILE);
        }

    if (file_seek(fp, mi.message_offset, 0) != 0)
        goto bad_message;

    if (buflen > (mi.size+1)) buflen = mi.size + 1;
    if (file_read(fp, buf, buflen-1) != buflen - 1)
        goto bad_read;

    buf[buflen-1] = 0;
	file_close(fp);
    return(0);
}
