/*************************************************************************
* unit:    struct            release 0.21                                *
*   struct.c - Provides some big and little endian abstraction functions,
*	      for manipulating with structures.
* Licency: GPL or LGPL                                                   *
*   Copyright (C) 1999-2026  Jaroslav Fojtik
**************************************************************************/
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>

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

#include "struct.h"

#if defined(uint64_t) || defined(uint64_t_defined)
 #define QWORD_ uint64_t
#else
 #define QWORD_ double
#endif


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

#ifndef BYTESWAP_uint64
 #define BYTESWAP_uint64(__pval) \
	uint8_t n; \
	uint8_t *x = (uint8_t *)&__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

#ifndef BYTESWAP_uint16
 #define BYTESWAP_uint16(__pval) __pval = \
	    ((__pval & 0xFF)<<8) | ((__pval & 0xFF00)>>8)
#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:     uint16_t *num : readed word  */
int RdWORD_LoEnd(uint16_t *num, FILE *f)
{
#ifdef LO_ENDIAN
  return(fread(num,1,2,f));
#else
#ifdef HI_ENDIAN
  const int ret = fread(num,1,2,f);
  *num = ((*num & 0xFF)<<8) | ((*num & 0xFF00)>>8);
return ret;
#else
  uint8_t b[2];
  const int i = fread(b,1,2,f);
  if(i>0) {*num=(uint16_t)b[0];
   if(i>1) *num+=(uint16_t)b[1] * 256l;
  }
  return i;
#endif
#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:     uint32_t *num : readed double word  */
int RdDWORD_LoEnd(uint32_t *pnum, FILE *f)
{
#ifdef LO_ENDIAN
  return(fread(pnum,1,4,f));
#else
#ifdef HI_ENDIAN
const int i = fread(pnum,1,4,f);
  BYTESWAP_uint32(*pnum);
return i;
#else
uint8_t b[4];
  const int i = fread(b,1,4,f);
  if(i>0) {*pnum=(uint32_t)b[0];
   if(i>1) {*pnum|=(uint32_t)b[1] * 256l;
    if(i>2) {*pnum|=(uint32_t)b[2] * 256l * 256l;
     if(i>3) *pnum|=(uint32_t)b[3] * 256l * 256l * 256l;
  }}}
  return i;
#endif
#endif
}
#endif


#if !defined(RdQWORD_LoEnd) && (defined(uint64_t_defined) || defined(LO_ENDIAN) || defined(HI_ENDIAN))
/* 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));
#else
#ifdef HI_ENDIAN
const int i = fread(num,1,8,f);
  BYTESWAP_uint64(*num);
return i;
#else
uint8_t b[8];
  const int i = fread(b,1,8,f);
  if(i>0) {*num=(QWORD_)b[0];
   if(i>1) {*num|=(QWORD_)b[1] * 256l;
    if(i>2) {*num|=(QWORD_)b[2] * 256l * 256l;
     if(i>3) {*num|=(QWORD_)b[3] * 256l * 256l * 256l;
      if(i>4) {*num|=(QWORD_)b[4] * 256l * 256l * 256l * 256l;
       if(i>5) {*num|=(QWORD_)b[5] * 256l * 256l * 256l * 256l * 256l;
        if(i>6) {*num|=(QWORD_)b[6] * 256l * 256l * 256l * 256l * 256l * 256l;
         if(i>7) *num|=(QWORD_)b[7] * 256l * 256l * 256l * 256l * 256l * 256l * 256l;
  }}}}}}}
  return i;
#endif
#endif
}
#endif


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


#ifndef RdDWORD_HiEnd
/* Read a double word from binary file in a HighEndian Format
 * parameters: FILE  *f :  binary input file
 * return:     uint32_t *num : readed double word  */
int RdDWORD_HiEnd(uint32_t *num, FILE *f)
{
#ifdef LO_ENDIAN
const int i = fread(num,1,4,f);
  BYTESWAP_uint32(*num);  
return i;
#else
#ifdef HI_ENDIAN
  return(fread(num,1,4,f));
#else		//endian invariant
uint8_t b[4];
  const int i = fread(b,1,4,f);
  if(i>0) {*num=(uint32_t)b[0] * 256l * 256l * 256l;
   if(i>1) {*num|=(uint32_t)b[1] * 256l * 256l;
    if(i>2) {*num|=(uint32_t)b[2] * 256l;
     if(i>3) *num|=(uint32_t)b[3];
  }}}
  return i;
#endif
#endif
}
#endif


#if !defined(RdQWORD_HiEnd) && (defined(uint64_t_defined) || defined(LO_ENDIAN) || defined(HI_ENDIAN))
/* Read a quad word from binary file in a HighEndian Format
 * parameters: FILE  *f :  binary input file
 * return:     uint32_t *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;
#else
#ifdef HI_ENDIAN
  return(fread(num,1,8,f));
#else
		//endian invariant
uint8_t b[8];
  const int i = fread(b,1,8,f);
  if(i>0) {*num=(uint64_t)b[0]  * 256l * 256l * 256l * 256l * 256l * 256l * 256l;
   if(i>1) {*num|=(uint64_t)b[1] * 256l * 256l * 256l * 256l * 256l * 256l;
    if(i>2) {*num|=(uint64_t)b[2] * 256l * 256l * 256l * 256l * 256l;
     if(i>3) {*num|=(uint64_t)b[3] * 256l * 256l * 256l * 256l;
      if(i>4) {*num|=(uint64_t)b[4] * 256l * 256l * 256l;
       if(i>5) {*num|=(uint64_t)b[5] * 256l * 256l;
        if(i>6) {*num|=(uint64_t)b[6] * 256l;
         if(i>7) *num|=(uint64_t)b[7];
  }}}}}}}
  return i;
#endif
#endif
}
#endif



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


#ifndef WrDWORD_LoEnd
/* Write a double word to the binary file in a LowEndian Format
 * parameters: FILE  *f :  binary input file
 *             uint32_t num : double word to be written */
int WrDWORD_LoEnd(uint32_t 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
uint8_t b[4];
  b[0] = (uint8_t)((num >> 8*0) & 0x00ffl);
  b[1] = (uint8_t)((num >> 8*1) & 0x00ffl);
  b[2] = (uint8_t)((num >> 8*2) & 0x00ffl);
  b[3] = (uint8_t)((num >> 8*3) & 0x00ffl);
  return fwrite(b, 1,4,f);
#endif
#endif
}
#endif


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


#ifndef WrDWORD_HiEnd
/* Write a double word to the binary file in a HiEndian Format
 * parameters: FILE  *f :  binary input file
 *             uint32_t num : double word to be written */
int WrDWORD_HiEnd(uint32_t 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
uint8_t b[4];
  b[0] = (uint8_t)((num >> 8*3) & 0x00FFl);
  b[1] = (uint8_t)((num >> 8*2) & 0x00FFl);
  b[2] = (uint8_t)((num >> 8*1) & 0x00FFl);
  b[3] = (uint8_t)((num >> 8*0) & 0x00FFl);
  return fwrite(b, 1,4,f);
#endif
#endif
}
#endif


#if !defined(WrQWORD_HiEnd) && (defined(uint64_t_defined) || defined(LO_ENDIAN) || defined(HI_ENDIAN))
/* Write a quad word to the binary file in a HiEndian Format
 * parameters: FILE  *f :  binary input file
 *             uint32_t 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
uint8_t b[8];
  b[0] = (uint8_t)((num >> 8*7) & 0x00FFl);
  b[1] = (uint8_t)((num >> 8*6) & 0x00FFl);
  b[2] = (uint8_t)((num >> 8*5) & 0x00FFl);
  b[3] = (uint8_t)((num >> 8*4) & 0x00FFl);
  b[4] = (uint8_t)((num >> 8*3) & 0x00FFl);
  b[5] = (uint8_t)((num >> 8*2) & 0x00FFl);
  b[6] = (uint8_t)((num >> 8*1) & 0x00FFl);
  b[7] = (uint8_t)((num >> 8*0) & 0x00FFl);
  return fwrite(b, 1,8,f);
#endif
#endif
}
#endif


#if !defined(WrQWORD_LoEnd) && (defined(uint64_t_defined) || defined(LO_ENDIAN) || defined(HI_ENDIAN))
/* Write a quad word to the binary file in a LowEndian Format
 * parameters: FILE  *f :  binary input file
 *             uint32_t 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
uint8_t b[8];
  b[0] = (uint8_t)((num >> 8*0) & 0x00FFl);
  b[1] = (uint8_t)((num >> 8*1) & 0x00FFl);
  b[2] = (uint8_t)((num >> 8*2) & 0x00FFl);
  b[3] = (uint8_t)((num >> 8*3) & 0x00FFl);
  b[4] = (uint8_t)((num >> 8*4) & 0x00FFl);
  b[5] = (uint8_t)((num >> 8*5) & 0x00FFl);
  b[6] = (uint8_t)((num >> 8*6) & 0x00FFl);
  b[7] = (uint8_t)((num >> 8*7) & 0x00FFl);
  return fwrite(b, 1,8,f);
#endif
#endif
}
#endif


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


/** Read a uint32_t from byte stream. */
uint32_t fil_sreadU32(const uint8_t *in)
{
#ifdef LO_ENDIAN
 return *(uint32_t *)in;
#else
 return (uint32_t)in[0] +
      ((uint32_t)in[1] << 8) +
      ((uint32_t)in[2] << 8*2) +
      ((uint32_t)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,uint16_t *),F);
     	      break;
     case 'W':loaded+=RdWORD_HiEnd(va_arg(ap,uint16_t *),F);
     	      break;
     case 'f':
     case 'd':loaded+=RdDWORD_LoEnd(va_arg(ap,uint32_t *),F);
     	      break;
     case 'F':
     case 'D':loaded+=RdDWORD_HiEnd(va_arg(ap,uint32_t *),F);
     	      break;
     case 'e':
     case 'q':loaded+=RdQWORD_LoEnd(va_arg(ap,QWORD_ *),F);
     	      break;
     case 'E':
     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' in loadstruct!", *description);
     }

   description++;
   }
 va_end(ap);

return(loaded);
}


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

 va_start(ap, description);

 while(*description!=0)
   {
   //printf("%c",*description);fflush(stdout);
   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':{const uint16_t w=va_arg(ap,uint16_t);
	      writed += WrWORD_LoEnd(w,F);}
     	      break;
     case 'W':writed += WrWORD_HiEnd(va_arg(ap,uint16_t),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':{const uint32_t d=va_arg(ap,uint32_t);
	      writed += WrDWORD_LoEnd(d,F);}
     	      break;
     case 'D':writed += WrDWORD_HiEnd(va_arg(ap,uint32_t),F);
     	      break;

#ifdef EllipsisPassesFloat	// Float is always passed as double https://stackoverflow.com/questions/11270588/variadic-function-va-arg-doesnt-work-with-float
     case 'f':{const float f = va_arg(ap,float);
              writed += WrDWORD_LoEnd(*(uint32_t*)&f,F);}
	      break;
     case 'F':{const float f = va_arg(ap,float);
              writed += WrDWORD_HiEnd(*(uint32_t*)&f,F);}
	      break;
#else
     case 'f':{const float f = va_arg(ap,double);		// float is stored on stack as double
              writed += WrDWORD_LoEnd(*(uint32_t*)&f,F);}
	      break;
     case 'F':{const float f = va_arg(ap,double);
              writed += WrDWORD_HiEnd(*(uint32_t*)&f,F);}
	      break;
#endif
     case 'a':
     case 'A':{int ArraySize=0;
              while(isdigit(*(++description)))
                 {
                 ArraySize=10*ArraySize + (int)(*description-'0');
                 }
              description--;
              writed += fwrite(va_arg(ap,const char *), 1, ArraySize,F);}
              break;
     case 'q':{const QWORD_ q = va_arg(ap,QWORD_);
	      writed+=WrQWORD_LoEnd(q,F);}
     	      break;
     case 'Q':writed+=WrQWORD_HiEnd(va_arg(ap,QWORD_),F);
     	      break;
     case 'e':{const double e = va_arg(ap,double);
              writed+=WrQWORD_LoEnd(*(QWORD_*)&e,F);}
     	      break;
     case 'E':{const double e = va_arg(ap,double);
              writed+=WrQWORD_HiEnd(*(QWORD_*)&e,F);}
     	      break;

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

   description++;
   }
 va_end(ap);

return(writed);
}
