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

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

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



/*\\\ FICHIERS INCLUDE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include <stdio.h>
#include	<stdlib.h>
#include	<string.h>

#include "archi.h"
#include "base.h"
#include "base_ct.h"
#include "log.h"
#include "memory.h"


/*\\\ CONSTANTES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#define	MEM_MAXMALLOC_MAXSIZE		65536L	/* Taille maximum autorisee par la routine MEM_max_malloc */



/*\\\ TYPES & STRUCTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ PROTOTYPES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ VARIABLES EXTERNES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ VARIABLES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

/*--------------------------------------------------------------------------*/
/*      Controle des allocations dynamiques de memoire                      */
/*--------------------------------------------------------------------------*/

#if ! defined (NDEBUG)
	long	MEM_cnt_successful_mallocs = 0;		/* Compteur total de mallocs */
	long	MEM_cnt_failed_mallocs = 0;			/* Compteur de mallocs echoues */
	long	MEM_cnt_successful_reallocs = 0;
	long	MEM_cnt_failed_reallocs = 0;
	long	MEM_lnb_mallocs = 0;		/* Compteur de mallocs simultanes */
#endif



/*\\\ FONCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: MEM_max_malloc                                                 */
/*      Description: Reserve le plus gros bloc possible de memoire. Si la   */
/*                   taille specifiee est impossible a allouer, essaie des  */
/*                   tailles plus petite. Si la reservation echoue meme     */
/*                   pour la taille minimale, renvoie un code d'erreur.     */
/*                   Les tailles essayees sont des puissances de 2 de la    */
/*                   taille minimale.                                       */
/*                   La taille maximum peut ne pas etre fixee (memoire      */
/*                   virtuelle). Pour ne pas trop utiliser de ressources,   */
/*                   la taille maximum est limitee par une constante.       */
/*      Parametres en entree:                                               */
/*        - max_bloc_len: Taille du bloc ideal en octets. -1 si aucune      */
/*                       limite (prend la plus grande zone possible).       */
/*        - min_block_len: Taille minimum a reserver. Elle peut etre plus   */
/*                         grande que max_block_len. Dans ce cas si max_ ne */
/*                         marche pas, on ne peut rien faire d'autre.       */
/*      Parametres en sortie:                                               */
/*        - block_len_ptr: pointeur sur la longueur finalement reservee.    */
/*      Retour: pointeur sur la nouvelle zone reservee, ou NULL si la zone  */
/*              n'a pas pu etre reservee correctement.                      */
/*==========================================================================*/

void	*MEM_max_malloc (signed long max_block_len, long min_block_len, long *block_len_ptr)
{
	long		block_len;
	long		old_block_len;
	void		*zone_ptr;
	void		*old_zone_ptr;

	if (max_block_len >= 0 && max_block_len <= MEM_MAXMALLOC_MAXSIZE)
	{
		zone_ptr = MALLOC (max_block_len);
		if (zone_ptr != NULL)
		{
			*block_len_ptr = max_block_len;
			return (zone_ptr);
		}
	}

	zone_ptr = MALLOC (min_block_len);
	if (zone_ptr == NULL)
	{
		return (NULL);
	}

	old_block_len = min_block_len;
	block_len = min_block_len * 2;
	while (block_len <= MEM_MAXMALLOC_MAXSIZE)
	{
		old_zone_ptr = zone_ptr;
		zone_ptr = REALLOC (zone_ptr, block_len);
		if (zone_ptr == NULL)
		{
			LOG_printf ("Pas de probleme (maxmalloc)\n");
			*block_len_ptr = old_block_len;
			return (old_zone_ptr);
		}
		old_block_len = block_len;
		block_len *= 2;
	}

	*block_len_ptr = old_block_len;
	return (zone_ptr);
}



/****************************************************************************/
/*                                                                          */
/*      OPERATIONS SUR LA MEMOIRE                                           */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom: MEM_invert_memory_byte                                         */
/*      Description: Inverse octet par octet une zone de la memoire.        */
/*      Parametres en entree:                                               */
/*        - ptr_zone: pointeur sur la zone a inverser                       */
/*        - length: longueur de la zone a inverser, en octets               */
/*==========================================================================*/

void	MEM_invert_memory_byte (void *ptr_zone, LWORD length)
{
	BYTE		tampon;
	BYTE		*ptr_1;
	BYTE		*ptr_2;

	ptr_1 = (BYTE *) ptr_zone;
	ptr_2 = ptr_1 + length - 1;
	for (length >>= 1; length > 0; length --)
	{
		tampon = *ptr_1;
		*ptr_1 = *ptr_2;
		*ptr_2 = tampon;
		ptr_1 ++;
		ptr_2 --;
	}
}



/*==========================================================================*/
/*      Nom: MEM_invert_memory_word                                         */
/*      Description: Inverse mot par mot une zone de la memoire.            */
/*      Parametres en entree:                                               */
/*        - ptr_zone: pointeur sur la zone a inverser                       */
/*        - length: longueur de la zone a inverser, en MOTS                 */
/*==========================================================================*/

void	MEM_invert_memory_word (void *ptr_zone, LWORD length)
{
	WORD		tampon;
	WORD		*ptr_1;
	WORD		*ptr_2;

	ptr_1 = (WORD *) ptr_zone;
	ptr_2 = ptr_1 + length - 1;
	for (length >>= 1; length > 0; length --)
	{
		tampon = *ptr_1;
		*ptr_1 = *ptr_2;
		*ptr_2 = tampon;
		ptr_1 ++;
		ptr_2 --;
	}
}



/*==========================================================================*/
/*      Nom: MEM_invert_memory_lword                                       */
/*      Description: Inverse mot long par mot long une zone de la memoire.  */
/*      Parametres en entree:                                               */
/*        - ptr_zone: pointeur sur la zone a inverser                       */
/*        - length: longueur de la zone a inverser, en MOTS LONGS           */
/*==========================================================================*/

void	MEM_invert_memory_lword (void *ptr_zone, LWORD length)
{
	LWORD		tampon;
	LWORD		*ptr_1;
	LWORD		*ptr_2;

	ptr_1 = (LWORD *) ptr_zone;
	ptr_2 = ptr_1 + length - 1;
	for (length >>= 1; length > 0; length --)
	{
		tampon = *ptr_1;
		*ptr_1 = *ptr_2;
		*ptr_2 = tampon;
		ptr_1 ++;
		ptr_2 --;
	}
}



/*==========================================================================*/
/*      Nom: MEM_rotate_memory                                              */
/*      Description: Fait tourner une zone de la memoire.                   */
/*                   ex: ABCDEFGHIJKLMNOP donnera:                          */
/*                       \\__                                               */
/*                          \\                                              */
/*                       MNOPABCDEFGHIJKL pour un deplacement de +4.        */
/*      Parametres en entree:                                               */
/*        - ptr_zone: pointeur sur la zone a decaler                        */
/*        - length: longueur de la zone a decaler, en octets                */
/*        - depl: taille du deplacement, en octets. Cette valeur est        */
/*                positive pour un deplacement vers la droite, negative     */
/*                pour un deplacement vers la gauche.                       */
/*==========================================================================*/

void	MEM_rotate_memory (void *ptr_zone, LWORD length, SLWORD depl)
{
	LWORD		buffer_size;
	void		*ptr_buffer;
	LWORD		offset;
	LWORD		depl2;

	if (depl == 0)
	{
		return;
	}

/*--- Essaie de faire la rotation avec un buffer ---------------------------*/

	buffer_size = ABS (depl);							/* Essaie en 1 passe */
	ptr_buffer = MALLOC (buffer_size);
	if (ptr_buffer == NULL)
	{
		buffer_size = (buffer_size + 1) / 2;		/* Sinon essaie en 2 passes */
		ptr_buffer = MALLOC (buffer_size);
	}

	if (ptr_buffer != NULL)			/* Si on peut le faire avec un buffer */
	{
		offset = ABS (depl);
		while (offset > 0)
		{
			depl2 = MIN (offset, buffer_size);
			if (depl >= 0)		/* Deplacement positif: droite */
			{
				memmove (ptr_buffer, (BYTE *)ptr_zone + length - depl2, depl2);
				memmove (ptr_zone, (BYTE *)ptr_zone + depl2, length - depl2);
				memmove (ptr_zone, ptr_buffer, depl2);
			}
			else					/* Deplacement negatif: gauche */
			{
				memmove (ptr_buffer, ptr_zone, depl2);
				memmove ((BYTE *)ptr_zone + depl2, ptr_zone, length - depl2);
				memmove ((BYTE *)ptr_zone + length - depl2, ptr_buffer, depl2);
			}
			offset -= depl2;
		}
		FREE (ptr_buffer);

		return;
	}

/*--- Sinon on essaie de le faire par inversions successives ---------------*/

	/* Teste si on peut optimiser l'affaire avec des mots */
	if (   ((ptrdiff_t)ptr_zone & 1) == 0
		 && ((int)length & 1) == 0
		 && ((int)depl & 1) == 0)
	{

		/* Ou meme avec des mots longs */
		if (   ((ptrdiff_t)ptr_zone & 3) == 0
			 && ((int)length & 3) == 0
			 && ((int)depl & 3) == 0)
		{
			length /= 4;
			depl /= 4;
			MEM_invert_memory_lword (ptr_zone, length);
			if (depl >= 0)		/* Deplacement positif: droite */
			{
				MEM_invert_memory_lword (ptr_zone, depl);
				MEM_invert_memory_lword ((LWORD *)ptr_zone + depl, length - depl);
			}
			else					/* Deplacement negatif: gauche */
			{
				MEM_invert_memory_lword (ptr_zone, length + depl);
				MEM_invert_memory_lword ((LWORD *)ptr_zone + length + depl, -depl);
			}
		}

		/* Rotation en mots simples */
		else
		{
			length /= 2;
			depl /= 2;
			MEM_invert_memory_word (ptr_zone, length);
			if (depl >= 0)		/* Deplacement positif: droite */
			{
				MEM_invert_memory_word (ptr_zone, depl);
				MEM_invert_memory_word ((WORD *)ptr_zone + depl, length - depl);
			}
			else					/* Deplacement negatif: gauche */
			{
				MEM_invert_memory_word (ptr_zone, length + depl);
				MEM_invert_memory_word ((WORD *)ptr_zone + length + depl, -depl);
			}
		}
	}

	/* Rotation en octets */
	else
	{
		MEM_invert_memory_byte (ptr_zone, length);
		if (depl >= 0)		/* Deplacement positif: droite */
		{
			MEM_invert_memory_byte (ptr_zone, depl);
			MEM_invert_memory_byte ((BYTE *)ptr_zone + depl, length - depl);
		}
		else					/* Deplacement negatif: gauche */
		{
			MEM_invert_memory_byte (ptr_zone, length + depl);
			MEM_invert_memory_byte ((BYTE *)ptr_zone + length + depl, -depl);
		}
	}
}



/****************************************************************************/
/*                                                                          */
/*      CONTROLE DES ALLOCATIONS DYNAMIQUES DE MEMOIRE                      */
/*__________________________________________________________________________*/
/*                                                                          */
/* Remplacement des fontions malloc(), realloc(), strdup() et free() par    */
/* les macros MALLOC(), REALLOC(), STRDUP() et FREE(), permettant un        */
/* controle des debordements des zones reservees.                           */
/****************************************************************************/



/*==========================================================================*/
/*      Nom: MEM_malloc                                                     */
/*      Description: Remplace le malloc() classique et reserve des tampons  */
/*                   de securite de part et d'autre du bloc pour detecter   */
/*                   les debordements lors du mfree().                      */
/*      Parametres en entree:                                               */
/*        - size: Taille en octets du bloc a reserver.                      */
/*      Retour: adresse de la zone reservee (NULL si echec).                */
/*==========================================================================*/

void *MEM_malloc (size_t size)
{
#if ! defined (NDEBUG)

	void		*p_manip;
	char		*pc_real;
	size_t	real_size;

	real_size = size + 8 + sizeof (size_t);

	pc_real = (char *) malloc (real_size);
	
	if (pc_real == NULL)
	{
		LOG_printf ("WARNING: malloc a echoue! %lu octets demandes.\n", size);
		MEM_cnt_failed_mallocs ++;
		p_manip = NULL;
	}

	else
	{
		MEM_lnb_mallocs ++;
		MEM_cnt_successful_mallocs ++;

		pc_real [0] = 'd';			/* debut */
		pc_real [1] = 'e';
		pc_real [2] = 'b';
		pc_real [3] = 'u';

		/* Sauve la taille de la zone alouee */
		*((size_t *) (pc_real + 4)) = real_size;

		pc_real [real_size-4] = 'f';		/* fin! */
		pc_real [real_size-3] = 'i';
		pc_real [real_size-2] = 'n';
		pc_real [real_size-1] = '!';


		/* Determine l'adresse de manipulation (celle du debut de la zone     */
		/* utilisateur) pour faire comme si on avait fait un malloc standard. */
		p_manip = pc_real + 4 + sizeof (size_t);

		/* Verifie l'integrite */
		MEM_mcheck (p_manip);
	}

	return (p_manip);

#else		// NDEBUG

	void		*p_manip;

	p_manip = malloc (size);

	return (p_manip);

#endif	// NDEBUG
}



/*==========================================================================*/
/*      Nom: MEM_strdup                                                     */
/*      Description: Remplace strdup() et alloue des tampons de securite,   */
/*                   comme MEM_malloc().                                    */
/*      Parametres en entree:                                               */
/*        - cpsz_src: pointeur sur la chaine a dupliquer.                   */
/*      Retour: pointeur sur la copie de la chaine, ou NULL si echec.       */
/*==========================================================================*/

char *MEM_strdup (const char *cpsz_src)
{
	size_t	size;
	char		*psz_dest;

	size = strlen (cpsz_src) + 1;
	psz_dest = (char *) MEM_malloc (size);
	if (psz_dest != NULL)
	{
		memcpy (psz_dest, cpsz_src, size);
		MCHECK (psz_dest);
	}

#if ! defined (NDEBUG)

	else
	{
		LOG_printf ("WARNING: malloc dans strdup impossible! %lu octets demandes.\n");
		MEM_cnt_failed_mallocs ++;
	}

#endif

	return (psz_dest);
}



/*==========================================================================*/
/*      Nom: MEM_realloc                                                    */
/*      Description: Remplace realloc et alloue des tampons de securite.    */
/*      Parametres en entree:                                               */
/*        - p_manip: pointeur sur une zone deja allouee.                    */
/*        - size: nouvelle taille voulue pour cette zone.                   */
/*      Retour: pointeur sur la nouvelle zone, NULL si echec.               */
/*==========================================================================*/

void *MEM_realloc (void *p_manip, size_t size)
{
#if ! defined (NDEBUG)

	char		*pc_real;
	size_t	real_size;

	if (p_manip == NULL)
	{
		LOG_printf ("ERREUR: realloc () interdit avec un pointeur NULL!\n");
		return NULL;
	}

	/* Verifie l'integrite */
	pc_real = (char *) MEM_mcheck (p_manip);
	if (pc_real == NULL)
	{
		LOG_printf ("REALLOC() impossible!\n");
		MEM_cnt_failed_reallocs ++;
		return (NULL);
	}
	MEM_cnt_successful_reallocs ++;

	real_size = size + 8 + sizeof (size_t);

	pc_real = (char *) realloc (pc_real, real_size);

	if (pc_real == NULL)
	{
		LOG_printf ("WARNING: realloc impossible! %lu octets demandes.\n", size);
		return (NULL);
	}

	*((size_t *) (pc_real + 4)) = real_size;

	/* Comme la taille a ete modifiee, on doit remettre le code a la fin. */
	pc_real [real_size-4] = 'f';			/* fin! */
	pc_real [real_size-3] = 'i';
	pc_real [real_size-2] = 'n';
	pc_real [real_size-1] = '!';

	/* Verifie l'integrite */
	p_manip = pc_real + 4 + sizeof (size_t);
	MEM_mcheck (p_manip);

	return (p_manip);

#else

	p_manip = realloc (p_manip, size);

	return (p_manip);

#endif
}



/*==========================================================================*/
/*      Nom: MEM_free                                                       */
/*      Description: Remplace free() pour les zones allouees avec           */
/*                   MEM_malloc() et MEM_strdup(). Verifie qu'il n'y a pas  */
/*                   eu de debordement.                                     */
/*      Parametres en entree:                                               */
/*        - p_manip: pointeur sur une zone memoire allouee.                 */
/*==========================================================================*/

void MEM_free(void *p_manip)
{
#if ! defined (NDEBUG)

	char		*pc_real;
	size_t	size;

	if (p_manip == NULL)
	{
		LOG_printf ("WARNING: On essaye de liberer un pointeur NULL avec free()!\n");
		return;
	}

	/* Verifie l'integrite */
	pc_real = (char *) MEM_mcheck (p_manip);
	if (pc_real == NULL)
	{
		LOG_printf ("FREE() impossible!\n");
		return;
	}

	/* Invalide la zone, ce qui permet de detecter un double-free */
	pc_real [0] = 'l';			/* "libre" */
	pc_real [1] = 'i';
	pc_real [2] = 'b';
	pc_real [3] = 'r';

	size = *( (size_t *)(pc_real+4) );

	pc_real [size-4] = 'f';		/* "free" */
	pc_real [size-3] = 'r';
	pc_real [size-2] = 'e';
	pc_real [size-1] = 'e';

	/* Liberation de la zone */
	free (pc_real);
	MEM_lnb_mallocs --;

#else

	free (p_manip);

#endif
}



#if ! defined (NDEBUG)

/*==========================================================================*/
/*      Nom: MEM_mcheck                                                     */
/*      Description: Verifie l'integrite d'une zone memoire allouee par     */
/*                   cette libraire. Renvoie la veritable adresse de la     */
/*                   zone allouee.                                          */
/*      Parametres en entree:                                               */
/*        - p_manip: adresse de manipulation.                               */
/*      Retour: l'adresse de la zone.                                       */
/*==========================================================================*/

void *MEM_mcheck (void *p_manip)
{
	char		*pc_real = (char *)p_manip - (4 + sizeof (size_t));
	size_t	size;

	if (   pc_real [0] != 'd'				/* debut */
		 || pc_real [1] != 'e'
		 || pc_real [2] != 'b'
		 || pc_real [3] != 'u' )
	{
		LOG_printf ("ERREUR: Zone corrompue au debut!\n");
		MEM_dump64 (pc_real);
	}

	size = *((size_t *) (pc_real + 4));

	if (   pc_real [size-4] != 'f'		/* fin! */
		 || pc_real [size-3] != 'i'
		 || pc_real [size-2] != 'n'
		 || pc_real [size-1] != '!' )
	{
		LOG_printf ("ERREUR: Zone corrompue a la fin!\n");
		MEM_dump64 (pc_real + size - 64);
	}
	
	return ((void *) pc_real);
}



/*==========================================================================*/
/*      Nom: MEM_memstat                                                    */
/*      Description: Statistiques sur l'utilisation de la memoire.          */
/*==========================================================================*/

void MEM_memstat (void)
{
	LOG_printf ("\nMemory use statistics:\n");
	LOG_printf ("\tNumber of successful malloc and strdup: %lu\n", MEM_cnt_successful_mallocs);
	LOG_printf ("\tNumber of successful realloc: %lu\n", MEM_cnt_successful_reallocs);
	LOG_printf ("\tNumber of failed mallocs and strdup: %lu\n", MEM_cnt_failed_mallocs);
	LOG_printf ("\tNumber of failed realloc: %lu\n", MEM_cnt_failed_reallocs);
	LOG_printf ("\tNumber of remaining malloc or strdup: %lu\n", MEM_lnb_mallocs);
}



/*==========================================================================*/
/*      Nom: MEM_dump64                                                     */
/*      Description: Affiche les 64 octets pointes sous forme ASCII.        */
/*      Parametres en entree:                                               */
/*        - dump_ptr: pointeur sur la zone a dumper.                        */
/*==========================================================================*/

void	MEM_dump64 (void *dump_ptr)
{
	int		line;
	int		counter;
	unsigned char	byte;
	unsigned char	*byte_ptr;

	byte_ptr = (unsigned char *) dump_ptr;

	for (line = 0; line < 4; line ++)
	{
		for (counter = 0; counter < 16; counter ++)
		{
			byte = byte_ptr [counter];
			LOG_printf ("%02X ", byte);
		}
		LOG_printf ("   ");

		for (counter = 0; counter < 16; counter ++)
		{
			byte = byte_ptr [counter];
			if (byte < 32 || byte > 127)
			{
				byte = ' ';
			}
			LOG_printf ("%c", byte);
		}
		LOG_printf ("\n");

		byte_ptr += 16;
	}
}

#endif



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/
