/**************************************************
* unit:    struct            release 0.12         *
*   STRUCT.C - Provides some big and little endian abstraction functions,
*	      for manipulating with structures.	
*   Copyright (C) 1999-2020  Jaroslav Fojtik
***************************************************/
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>

#include "common.h"
#include "typedfs.h"

#include "struct.h"

#ifdef QWORD
 #define QWORD_ QWORD
#else
 #define QWORD_ double
#endif


#if __GNUC__>4 || (__GNUC__==4 && __GNUC_MINOR__>=3)
 #define BYTESWAP_uint64(__pval) __pval=__builtin_bswap64(__pval)
 #define BYTESWAP_uint32(__pval) __pval=__builtin_bswap32(__pval)
#else
 #if _MSC_VER > 1200	/* Not present in MSVC6 or less. */
  #include <stdlib.h>
  #define BYTESWAP_uint64(__pval) __pval=_byteswap_uint64(__pval)
 #endif
#endif

#ifndef BYTESWAP_uint64
 #define BYTESWAP_uint64(__pval) \
	BYTE n; \
	BYTE *x = (BYTE *)&__pval; \
	n=x[0]; x[0]=x[7]; x[7]=n; \
	n=x[1]; x[1]=x[6]; x[6]=n; \
	n=x[2]; x[2]=x[5]; x[5]=n; \
	n=x[3]; x[3]=x[4]; x[4]=n
#endif

#ifndef BYTESWAP_uint32
 #define BYTESWAP_uint32(__pval) __pval = \
	    ((__pval & 0xFF)<<24) | ((__pval & 0xFF00)<<8) | ((__pval & 0xFF0000)>>8) | ((__pval & 0xFF000000)>>24)
#endif



/* These procedures should be safe for all cases */

#ifndef RdWORD_LoEnd
/* Read a word from binary file in a LowEndian Format into a current endian.
 * parameters: FILE  *f :  binary input file
 * return:     WORD *num : readed word  */
int RdWORD_LoEnd(WORD *num, FILE *f)
{
#ifdef LO_ENDIAN
  return(fread(num,1,2,f));
#endif
#ifdef HI_ENDIAN
int ret = fread(num,1,2,f);
  *num = ((*num & 0xFF)<<8) | ((*num & 0xFF00)>>8);
return ret;
#endif
#if !defined(LO_ENDIAN) && !defined(HI_ENDIAN)
unsigned char b;
  if(!fread( &b, 1,1,f)) return 0; *num=(WORD)b;
  if(!fread( &b, 1,1,f)) return 1; *num+=(WORD)b * 256l;
return 2;
#endif
}
#endif


#ifndef RdDWORD_LoEnd
/* Read a double word from binary file in a LowEndian Format into a current endian.
 * parameters: FILE  *f :  binary input file
 * return:     DWORD *num : readed double word  */
int RdDWORD_LoEnd(DWORD *num, FILE *f)
{
#ifdef LO_ENDIAN
  return(fread(num,1,4,f));
#endif
#ifdef HI_ENDIAN
const int i = fread(num,1,4,f);
  BYTESWAP_uint32(*num);
return i;
#endif
#if !defined(LO_ENDIAN) && !defined(HI_ENDIAN)
unsigned char b;
  if(!fread( &b, 1,1,f)) return 0; *num=(DWORD)b;
  if(!fread( &b, 1,1,f)) return 1; *num+=(DWORD)b * 256l;
  if(!fread( &b, 1,1,f)) return 2; *num+=(DWORD)b * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 3; *num+=(DWORD)b * 256l * 256l * 256l;
return 4;
#endif
}
#endif


#ifndef RdQWORD_LoEnd
/* Read a double word from binary file in a LowEndian Format into a current endian.
 * parameters: FILE  *f :  binary input file
 * return:     QWORD *num : readed double word  */
int RdQWORD_LoEnd(QWORD_ *num, FILE *f)
{
#ifdef LO_ENDIAN
  return(fread(num,1,8,f));
#endif
#ifdef HI_ENDIAN
const int i = fread(num,1,8,f);
  BYTESWAP_uint64(*num);
return i;
#endif
#if !defined(LO_ENDIAN) && !defined(HI_ENDIAN)
BYTE b;
  if(!fread( &b, 1,1,f)) return 0; *num=(QWORD_)b;
  if(!fread( &b, 1,1,f)) return 1; *num+=(QWORD_)b * 256l;
  if(!fread( &b, 1,1,f)) return 2; *num+=(QWORD_)b * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 3; *num+=(QWORD_)b * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 4; *num+=(QWORD_)b * 256l * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 5; *num+=(QWORD_)b * 256l * 256l * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 6; *num+=(QWORD_)b * 256l * 256l * 256l * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 7; *num+=(QWORD_)b * 256l * 256l * 256l * 256l * 256l * 256l * 256l;
return 8;  
#endif
}
#endif


#ifndef RdWORD_HiEnd
/* Read a word from binary file in a HiEndian Format
 * parameters: FILE  *f :  binary input file
 * return:     WORD *num : readed word  */
int RdWORD_HiEnd(WORD *num, FILE *f)
{
#ifdef LO_ENDIAN
int ret = fread(num,1,2,f);
  *num = ((*num & 0xFF)<<8) | ((*num & 0xFF00)>>8);
return ret;
#endif
#ifdef HI_ENDIAN
  return(fread(num,1,2,f));
#endif
#if !defined(LO_ENDIAN) && !defined(HI_ENDIAN)
		//endian invariant
unsigned char b;
  if(!fread(&b,1,1,f)) return 0; *num=(WORD)b * 256l;
  if(!fread(&b,1,1,f)) return 1; *num+=(WORD)b;
return 2;
#endif
}
#endif


#ifndef RdDWORD_HiEnd
/* Read a double word from binary file in a HighEndian Format
 * parameters: FILE  *f :  binary input file
 * return:     DWORD *num : readed double word  */
int RdDWORD_HiEnd( DWORD *num, FILE *f )
{
#ifdef LO_ENDIAN
const int i = fread(num,1,4,f);
  BYTESWAP_uint32(*num);  
return i;
#endif
#ifdef HI_ENDIAN
  return(fread(num,1,4,f));
#endif
#if !defined(LO_ENDIAN) && !defined(HI_ENDIAN)
		//endian invariant
BYTE b;
  if(!fread( &b, 1,1,f)) return 0; *num=(DWORD)b * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 1; *num+=(DWORD)b * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 2; *num+=(DWORD)b * 256l;
  if(!fread( &b, 1,1,f)) return 3; *num+=(DWORD)b;
return 4;
#endif
}
#endif


#ifndef RdQWORD_HiEnd
/* Read a quad word from binary file in a HighEndian Format
 * parameters: FILE  *f :  binary input file
 * return:     DWORD *num : readed double word  */
int RdQWORD_HiEnd(QWORD_ *num, FILE *f )
{
#ifdef LO_ENDIAN
const int i = fread(num,1,8,f);
  BYTESWAP_uint64(*num);
return i;
#endif
#ifdef HI_ENDIAN
  return(fread(num,1,8,f));
#endif
#if !defined(LO_ENDIAN) && !defined(HI_ENDIAN)
		//endian invariant
BYTE b;
  if(!fread( &b, 1,1,f)) return 0; *num=(DWORD)b  * 256l * 256l * 256l * 256l * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 1; *num+=(DWORD)b * 256l * 256l * 256l * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 2; *num+=(DWORD)b * 256l * 256l * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 3; *num+=(DWORD)b * 256l * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 4; *num+=(DWORD)b * 256l * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 5; *num+=(DWORD)b * 256l * 256l;
  if(!fread( &b, 1,1,f)) return 6; *num+=(DWORD)b * 256l;
  if(!fread( &b, 1,1,f)) return 7; *num+=(DWORD)b;
  return 8;
#endif
}
#endif



#ifndef WrWORD_LoEnd
/* Write a word to the binary file in a LowEndian Format
 * parameters: FILE  *f :  binary input file
 *             WORD num : word to be written */
int WrWORD_LoEnd(WORD num, FILE *f)
{
#ifdef LO_ENDIAN
  return(fwrite(&num,1,2,f));
#else
BYTE b;
  b = (BYTE)((num >> 8*0) & 0x00ffl); if(!fwrite( &b, 1,1,f)) return 0;
  b = (BYTE)((num >> 8*1) & 0x00ffl); if(!fwrite( &b, 1,1,f)) return 1;
return 2;
#endif
}
#endif


#ifndef WrDWORD_LoEnd
/* Write a double word to the binary file in a LowEndian Format
 * parameters: FILE  *f :  binary input file
 *             DWORD num : double word to be written */
int WrDWORD_LoEnd(DWORD num, FILE *f)
{
#ifdef LO_ENDIAN
  return(fwrite(&num,1,4,f));
#else
#ifdef HI_ENDIAN
  BYTESWAP_uint32(num);
  return(fwrite(&num,1,4,f));
#else
BYTE b;
  b = (BYTE)((num >> 8*0) & 0x00ffl); if(!fwrite( &b, 1,1,f)) return 0;
  b = (BYTE)((num >> 8*1) & 0x00ffl); if(!fwrite( &b, 1,1,f)) return 1;
  b = (BYTE)((num >> 8*2) & 0x00ffl); if(!fwrite( &b, 1,1,f)) return 2;
  b = (BYTE)((num >> 8*3) & 0x00ffl); if(!fwrite( &b, 1,1,f)) return 3;
return 4;
#endif
#endif
}
#endif


#ifndef WrWORD_HiEnd
/* Write a word to the binary file in a HighEndian Format
 * parameters: FILE  *f :  binary input file
 *             WORD num : word to be written */
int WrWORD_HiEnd( WORD num, FILE *f )
{
#ifdef HI_ENDIAN
  return(fwrite(&num,1,2,f));
#else
BYTE b;
  b = (BYTE)((num >> 8*1) & 0x00ffl); if(!fwrite( &b, 1,1,f)) return 0;
  b = (BYTE)((num >> 8*0) & 0x00ffl); if(!fwrite( &b, 1,1,f)) return 1;
return 2;
#endif
}
#endif


#ifndef WrDWORD_HiEnd
/* Write a double word to the binary file in a HiEndian Format
 * parameters: FILE  *f :  binary input file
 *             DWORD num : double word to be written */
int WrDWORD_HiEnd(DWORD num, FILE *f )
{
#ifdef HI_ENDIAN
  return(fwrite(&num,1,4,f));
#else
#ifdef LO_ENDIAN
  BYTESWAP_uint32(num);
  return(fwrite(&num,1,4,f));
#else
BYTE b;
  b = (BYTE)((num >> 8*3) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 0;
  b = (BYTE)((num >> 8*2) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 1;
  b = (BYTE)((num >> 8*1) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 2;
  b = (BYTE)((num >> 8*0) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 3;
return 4;
#endif
#endif
}
#endif



#ifndef WrQWORD_HiEnd
/* Write a quad word to the binary file in a HiEndian Format
 * parameters: FILE  *f :  binary input file
 *             DWORD num : double word to be written */
int WrQWORD_HiEnd(QWORD_ num, FILE *f )
{
#ifdef HI_ENDIAN
  return(fwrite(&num,1,8,f));
#else
#ifdef LO_ENDIAN
  BYTESWAP_uint64(num);
  return(fwrite(&num,1,8,f));
#else
BYTE b;
  b = (BYTE)((num >> 8*7) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 0;    
  b = (BYTE)((num >> 8*6) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 1;
  b = (BYTE)((num >> 8*5) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 2;
  b = (BYTE)((num >> 8*4) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 3;  
  b = (BYTE)((num >> 8*3) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 4;
  b = (BYTE)((num >> 8*2) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 5;
  b = (BYTE)((num >> 8*1) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 6;
  b = (BYTE)((num >> 8*0) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 7;
return 8;
#endif
#endif
}
#endif


#ifndef WrQWORD_LoEnd
/* Write a quad word to the binary file in a LowEndian Format
 * parameters: FILE  *f :  binary input file
 *             DWORD num : double word to be written */
int WrQWORD_LoEnd(QWORD_ num, FILE *f )
{
#ifdef LO_ENDIAN
  return(fwrite(&num,1,8,f));
#else
#ifdef HI_ENDIAN
  BYTESWAP_uint64(num);
  return(fwrite(&num,1,8,f));
#else
BYTE b;
  b = (BYTE)((num >> 8*0) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 0;
  b = (BYTE)((num >> 8*1) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 1;
  b = (BYTE)((num >> 8*2) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 2;
  b = (BYTE)((num >> 8*3) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 3;  
  b = (BYTE)((num >> 8*4) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 4;
  b = (BYTE)((num >> 8*5) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 5;
  b = (BYTE)((num >> 8*6) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 6;
  b = (BYTE)((num >> 8*7) & 0x00FFl); if(!fwrite(&b, 1,1,f)) return 7;    
return 8;
#endif
#endif
}
#endif


/** Read a WORD from byte stream. */
WORD fil_sreadU16 (const BYTE *in)
{
#ifdef LO_ENDIAN
 return *(WORD *)in;
#else
 return (WORD)in[0] +
       ((WORD)in[1] << 8);
#endif
}


/** Read a DWORD from byte stream. */
DWORD fil_sreadU32 (const BYTE *in)
{
#ifdef LO_ENDIAN
 return *(DWORD *)in;
#else
 return (DWORD)in[0] +
      ((DWORD)in[1] << 8) +
      ((DWORD)in[2] << 8*2) +
      ((DWORD)in[3] << 8*3);
#endif
}



int loadstruct(FILE *F,const char *description, ...)
{
 va_list ap;
 int loaded=0;
 int ArraySize;

 va_start(ap, description);
 while(*description!=0)
   {

   switch(*description)
     {
     case 'b':
     case 'B':loaded+=fread(va_arg(ap,char *),1,1,F);
              break;
     case 'w':loaded+=RdWORD_LoEnd(va_arg(ap,WORD *),F);
     	      break;
     case 'W':loaded+=RdWORD_HiEnd(va_arg(ap,WORD *),F);
     	      break;
     case 'd':loaded+=RdDWORD_LoEnd(va_arg(ap,DWORD *),F);
     	      break;
     case 'D':loaded+=RdDWORD_HiEnd(va_arg(ap,DWORD *),F);
     	      break;
     case 'q':loaded+=RdQWORD_LoEnd(va_arg(ap,QWORD_ *),F);
     	      break;
     case 'Q':loaded+=RdQWORD_HiEnd(va_arg(ap,QWORD_ *),F);
     	      break;

     case 'a':
     case 'A':ArraySize=0;
     	      while (isdigit(*(++description)))
	         {
                 ArraySize = 10*ArraySize + (int)(*description-'0');
                 }
              description--;

	      loaded+=fread(va_arg(ap,char *),1,ArraySize,F);
              break;


     default:printf("Error: Unknown character '%c'!", *description);
     }

   description++;
   }
 va_end(ap);

return(loaded);
}


int savestruct(FILE *F,const char *description, ...)
{
 va_list ap;
 int writed=0;
 int ArraySize;

 va_start(ap, description);
 while(*description!=0)
   {

   switch(*description)
     {     
#ifndef __Ellipsis_Expand_Short
     case 'b':
     case 'B':{const char c = va_arg(ap,char);
	      writed+=fwrite(&c,1,1,F);}
              break;	
     case 'w':writed+=WrWORD_LoEnd( va_arg(ap,WORD),F);
     	      break;
     case 'W':writed+=WrWORD_HiEnd( va_arg(ap,WORD),F);
     	      break;
#else	//all shorts are internally converted to int
     case 'b':
     case 'B':{char c=va_arg(ap,int);
	      writed+=fwrite(&c,1,1,F);	      
              break;}
     case 'w':writed+=WrWORD_LoEnd( va_arg(ap,int),F);
     	      break;
     case 'W':writed+=WrWORD_HiEnd( va_arg(ap,int),F);
     	      break;
#endif
     case 'd':writed+=WrDWORD_LoEnd( va_arg(ap,DWORD),F);
     	      break;
     case 'D':writed+=WrDWORD_HiEnd( va_arg(ap,DWORD),F);
     	      break;

     case 'a':
     case 'A':ArraySize=0;
     	      while (isdigit(*(++description)))
	         {
                 ArraySize=10*ArraySize + (int)(*description-'0');
                 }
              description--;

              writed+=fwrite(va_arg(ap,char *),1,ArraySize,F);
              break;
     case 'q':writed+=WrQWORD_LoEnd( va_arg(ap,QWORD_),F);
     	      break;
     case 'Q':writed+=WrQWORD_HiEnd( va_arg(ap,QWORD_),F);
     	      break;

     default:printf("Error: Unknown character %c !", *description);
     }

   description++;
   }
 va_end(ap);

return(writed);
}

