LOP/
LOP/area/
LOP/boards/
LOP/channels/
LOP/clans/
LOP/classes/
LOP/color/
LOP/councils/
LOP/deity/
LOP/races/
LOP/src/specials/
/*****************************************************************************
 * DikuMUD (C) 1990, 1991 by:                                                *
 *   Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen,   *
 *   and Katja Nyboe.                                                        *
 *---------------------------------------------------------------------------*
 * MERC 2.1 (C) 1992, 1993 by:                                               *
 *   Michael Chastain, Michael Quan, and Mitchell Tse.                       *
 *---------------------------------------------------------------------------*
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider.                    *
 *   Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
 *         gorog, Grishnakh, Nivek, Tricops, and Fireblade.                  *
 *---------------------------------------------------------------------------*
 * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community.              *
 *                    Their contributions are greatly appreciated.           *
 *---------------------------------------------------------------------------*
 * LoP (C) 2006, 2007, 2008 by: the LoP team.                                *
 *---------------------------------------------------------------------------*
 * 			Table load/save Module				     *
 *****************************************************************************/

#include <stdio.h>
#include <string.h>
#if !defined(WIN32)
   #include <dlfcn.h>
#else
   #include <windows.h>
   #define dlsym( handle, name ) ( (void*)GetProcAddress( (HINSTANCE) (handle), (name) ) )
   #define dlerror() GetLastError()
#endif
#include "h/mud.h"

bool load_race_file( const char *fname );
void write_race_file( int ra );

/* global variables */
int top_sn;
int top_herb;
int MAX_PC_CLASS;
int MAX_PC_RACE;

SKILLTYPE *skill_table[MAX_SKILL];
SKILLTYPE *herb_table[MAX_HERB];

LANG_DATA *first_lang, *last_lang;

char *const skill_tname[] =
{
   "unknown", "Spell", "Skill",  "Weapon",
   "Tongue",  "Herb"
};

SPELL_FUN *spell_function( char *name )
{
   void *funHandle;
#if !defined(WIN32)
   const char *error;
#else
   DWORD error;
#endif

   funHandle = dlsym( sysdata.dlHandle, name );
   if( ( error = dlerror() ) )
   {
      bug( "Error locating %s in symbol table. %s", name, error );
      return spell_notfound;
   }
   return (SPELL_FUN*)funHandle;
}

DO_FUN *skill_function( char *name )
{
   void *funHandle;
#if !defined(WIN32)
   const char *error;
#else
   DWORD error;
#endif

   funHandle = dlsym( sysdata.dlHandle, name );
   if( ( error = dlerror() ) )
   {
      bug( "Error locating %s in symbol table. %s", name, error );
      return skill_notfound;
   }
   return (DO_FUN*)funHandle;
}

/* Function used by qsort to sort skills */
int skill_comp( SKILLTYPE ** sk1, SKILLTYPE ** sk2 )
{
   SKILLTYPE *skill1 = ( *sk1 );
   SKILLTYPE *skill2 = ( *sk2 );

   if( !skill1 && skill2 )
      return 1;
   if( skill1 && !skill2 )
      return -1;
   if( !skill1 && !skill2 )
      return 0;
   if( skill1->type < skill2->type )
      return -1;
   if( skill1->type > skill2->type )
      return 1;
   return strcasecmp( skill1->name, skill2->name );
}

/* Sort the skill table with qsort */
void sort_skill_table( void )
{
   if( top_sn <= 0 )
      return;
   log_string( "Sorting skill table..." );
   qsort( &skill_table[1], top_sn - 1, sizeof( SKILLTYPE * ), ( int ( * )( const void *, const void * ) )skill_comp );
}

/* Remap slot numbers to sn values */
void remap_slot_numbers( void )
{
   SKILLTYPE *skill;
   SMAUG_AFF *aff;
   char tmp[32];
   int sn;

   log_string( "Remapping slots to sns" );
   gsn_first_spell = -1;
   gsn_first_skill = -1;
   gsn_first_weapon = -1;
   gsn_first_tongue = -1;
   gsn_top_sn = top_sn;

   for( sn = 0; sn <= top_sn; sn++ )
   {
      if( ( skill = skill_table[sn] ) )
      {
         if( gsn_first_spell == -1 && skill->type == SKILL_SPELL )
            gsn_first_spell = sn;
         else if( gsn_first_skill == -1 && skill->type == SKILL_SKILL )
            gsn_first_skill = sn;
         else if( gsn_first_weapon == -1 && skill->type == SKILL_WEAPON )
            gsn_first_weapon = sn;
         else if( gsn_first_tongue == -1 && skill->type == SKILL_TONGUE )
            gsn_first_tongue = sn;

         for( aff = skill->first_affect; aff; aff = aff->next )
         {
            if( aff->location == APPLY_WEAPONSPELL || aff->location == APPLY_WEARSPELL
            || aff->location == APPLY_REMOVESPELL  || aff->location == APPLY_STRIPSN )
            {
               snprintf( tmp, sizeof( tmp ), "%d", slot_lookup( atoi( aff->modifier ) ) );
               STRFREE( aff->modifier );
               aff->modifier = STRALLOC( tmp );
            }
         }
      }
   }
}

/* Write skill data to a file */
void fwrite_skill( FILE *fp, SKILLTYPE *skill )
{
   SMAUG_AFF *aff;
   int modifier;

   fprintf( fp, "Name         %s~\n", skill->name );
   fprintf( fp, "Type         %s\n", skill_tname[skill->type] );
   if( skill->damage )
      fprintf( fp, "SDamage      %s~\n", spell_damage[skill->damage] );
   if( skill->action )
      fprintf( fp, "SAction      %s~\n", spell_action[skill->action] );
   if( skill->Class )
      fprintf( fp, "SClass       %s~\n", spell_class[skill->Class] );
   if( skill->power )
      fprintf( fp, "SPower       %s~\n", spell_power[skill->power] );
   if( skill->save )
      fprintf( fp, "SSave        %s~\n", spell_save_effect[skill->save] );
   if( !xIS_EMPTY( skill->flags ) )
      fprintf( fp, "Flags        %s~\n", ext_flag_string( &skill->flags, spell_flag ) );
   if( skill->target )
      fprintf( fp, "Target       %s~\n", target_type[skill->target] );
   if( skill->minimum_position )
      fprintf( fp, "Minpos       %s~\n", pos_names[skill->minimum_position] );
   if( !xIS_EMPTY( skill->spell_sector ) )
      fprintf( fp, "Ssector      %s~\n", ext_flag_string( &skill->spell_sector, sect_flags ) );
   if( skill->saves )
      fprintf( fp, "Saves        %d\n", skill->saves );
   if( skill->slot )
      fprintf( fp, "Slot         %d\n", skill->slot );
   if( skill->min_mana )
      fprintf( fp, "Mana         %d\n", skill->min_mana );
   if( skill->beats )
      fprintf( fp, "Rounds       %d\n", skill->beats );
   if( skill->range )
      fprintf( fp, "Range        %d\n", skill->range );
   if( skill->skill_fun )
      fprintf( fp, "Code         %s\n", skill->skill_fun_name );
   else if( skill->spell_fun )
      fprintf( fp, "Code         %s\n", skill->spell_fun_name );
   if( skill->noun_damage )
      fprintf( fp, "Dammsg       %s~\n", skill->noun_damage );
   if( skill->msg_off )
      fprintf( fp, "Wearoff      %s~\n", skill->msg_off );

   if( skill->hit_char )
      fprintf( fp, "Hitchar      %s~\n", skill->hit_char );
   if( skill->hit_vict )
      fprintf( fp, "Hitvict      %s~\n", skill->hit_vict );
   if( skill->hit_room )
      fprintf( fp, "Hitroom      %s~\n", skill->hit_room );
   if( skill->hit_dest )
      fprintf( fp, "Hitdest      %s~\n", skill->hit_dest );

   if( skill->miss_char )
      fprintf( fp, "Misschar     %s~\n", skill->miss_char );
   if( skill->miss_vict )
      fprintf( fp, "Missvict     %s~\n", skill->miss_vict );
   if( skill->miss_room )
      fprintf( fp, "Missroom     %s~\n", skill->miss_room );

   if( skill->die_char )
      fprintf( fp, "Diechar      %s~\n", skill->die_char );
   if( skill->die_vict )
      fprintf( fp, "Dievict      %s~\n", skill->die_vict );
   if( skill->die_room )
      fprintf( fp, "Dieroom      %s~\n", skill->die_room );

   if( skill->imm_char )
      fprintf( fp, "Immchar      %s~\n", skill->imm_char );
   if( skill->imm_vict )
      fprintf( fp, "Immvict      %s~\n", skill->imm_vict );
   if( skill->imm_room )
      fprintf( fp, "Immroom      %s~\n", skill->imm_room );

   if( skill->dice )
      fprintf( fp, "Dice         %s~\n", skill->dice );
   if( skill->value )
      fprintf( fp, "Value        %d\n", skill->value );
   if( skill->difficulty )
      fprintf( fp, "Difficulty   %d\n", skill->difficulty );
   if( skill->participants )
      fprintf( fp, "Participants %d\n", skill->participants );
   if( skill->components )
      fprintf( fp, "Components   %s~\n", skill->components );
   if( skill->teachers )
      fprintf( fp, "Teachers     %s~\n", skill->teachers );
   for( aff = skill->first_affect; aff; aff = aff->next )
   {
      fprintf( fp, "Affect       '%s' '%s' %d ",
         aff->duration ? aff->duration : "0", aff->location ? a_types[aff->location % REVERSE_APPLY] : "0",
         aff->location >= REVERSE_APPLY ? 1 : 0 );
      modifier = atoi( aff->modifier );
      if( ( ( aff->location % REVERSE_APPLY ) >= APPLY_WEAPONSPELL
      && ( aff->location % REVERSE_APPLY ) <= APPLY_STRIPSN )
      && is_valid_sn( modifier ) )
         fprintf( fp, "'%d' ", skill_table[modifier]->slot );
      /* Change it back to a string */
      else if( ( ( aff->location % REVERSE_APPLY ) >= APPLY_RESISTANT
      && ( aff->location % REVERSE_APPLY ) <= APPLY_SUSCEPTIBLE )
      && modifier >= 0 && modifier < RIS_MAX )
         fprintf( fp, "'%s' ", ris_flags[modifier] );
      else if( ( aff->location % REVERSE_APPLY ) == APPLY_EXT_AFFECT
      && modifier >= 0 && modifier < AFF_MAX )
         fprintf( fp, "'%s' ", a_flags[modifier] );
      else
         fprintf( fp, "'%s' ", aff->modifier );
      if( aff->bitvector >= 0 && aff->bitvector < AFF_MAX )
         fprintf( fp, "'%s'\n", a_flags[aff->bitvector] );
      else
         fprintf( fp, "'%d'\n", aff->bitvector );
   }

   fprintf( fp, "End\n\n" );
}

/* Save the skill table to disk */
void save_skill_table( void )
{
   int x;
   FILE *fp;

   if( !( fp = fopen( SKILL_FILE, "w" ) ) )
   {
      bug( "%s: Can't open %s for writting", __FUNCTION__, SKILL_FILE );
      perror( SKILL_FILE );
      return;
   }

   for( x = 0; x < top_sn; x++ )
   {
      if( !skill_table[x]->name || skill_table[x]->name[0] == '\0' )
         break;
      fprintf( fp, "#SKILL\n" );
      fwrite_skill( fp, skill_table[x] );
   }
   fprintf( fp, "#END\n" );
   fclose( fp );
   fp = NULL;
}

/* Save the herb table to disk */
void save_herb_table( void )
{
   int x;
   FILE *fp;

   if( !( fp = fopen( HERB_FILE, "w" ) ) )
   {
      bug( "Can't open %s for writting", HERB_FILE );
      perror( HERB_FILE );
      return;
   }

   for( x = 0; x < top_herb; x++ )
   {
      if( !herb_table[x]->name )
         break;
      fprintf( fp, "#HERB\n" );
      fwrite_skill( fp, herb_table[x] );
   }
   fprintf( fp, "#END\n" );
   fclose( fp );
   fp = NULL;
}

int get_skill( char *skilltype )
{
   if( !str_cmp( skilltype, "Spell" ) )
      return SKILL_SPELL;
   if( !str_cmp( skilltype, "Skill" ) )
      return SKILL_SKILL;
   if( !str_cmp( skilltype, "Weapon" ) )
      return SKILL_WEAPON;
   if( !str_cmp( skilltype, "Tongue" ) )
      return SKILL_TONGUE;
   if( !str_cmp( skilltype, "Herb" ) )
      return SKILL_HERB;
   return SKILL_UNKNOWN;
}

SKILLTYPE *fread_skill( FILE *fp )
{
   const char *word;
   bool fMatch;
   SKILLTYPE *skill;
   int x, value;
   char *infoflags, flag[MSL];

   CREATE( skill, SKILLTYPE, 1 );
   skill->slot = 0;
   skill->min_mana = 0;
   for( x = 0; x < MAX_CLASS; x++ )
   {
      skill->skill_level[x] = -1;
      skill->skill_adept[x] = 95;
   }
   for( x = 0; x < MAX_RACE; x++ )
   {
      skill->race_level[x] = -1;
      skill->race_adept[x] = 95;
   }
   skill->target = 0;
   skill->skill_fun = NULL;
   skill->spell_fun = NULL;
   xCLEAR_BITS( skill->spell_sector );

   for( ;; )
   {
      word = feof( fp ) ? "End" : fread_word( fp );
      fMatch = false;

      switch( UPPER( word[0] ) )
      {
         case '*':
            fMatch = true;
            fread_to_eol( fp );
            break;

         case 'A':
            if( !str_cmp( word, "Affect" ) )
            {
               SMAUG_AFF *aff;
               char modifier[MIL];
               bool dadd = false;

               CREATE( aff, SMAUG_AFF, 1 );
               aff->duration = STRALLOC( fread_word( fp ) );

               infoflags = fread_word( fp );
               if( str_cmp( infoflags, "0" ) )
               {
                  value = get_flag( infoflags, a_types, APPLY_MAX );
                  if( value < 0 || value >= APPLY_MAX )
                  {
                     bug( "%s(%s): Unknown apply %s",
                        __FUNCTION__, skill->name ? skill->name : "Unknown", infoflags );
                     aff->location = 0;
                     dadd = true;
                  }
                  else
                     aff->location = value;
               }
               else
                  aff->location = 0;

               value = fread_number( fp );
               if( value == 1 )
                  aff->location += REVERSE_APPLY;

               infoflags = fread_word( fp );
               if( ( aff->location % REVERSE_APPLY ) == APPLY_EXT_AFFECT )
               {
                  value = get_flag( infoflags, a_flags, AFF_MAX );
                  if( value < 0 || value >= AFF_MAX )
                  {
                     bug( "%s(%s): Unknown affect %s", __FUNCTION__, skill->name ? skill->name : "Unknown",
                        infoflags );
                     aff->modifier = STRALLOC( "-1" );
                     dadd = true;
                  }
                  else
                  {
                     snprintf( modifier, sizeof( modifier ), "%d", value );
                     aff->modifier = STRALLOC( modifier );
                  }
               }
               else if( ( aff->location % REVERSE_APPLY ) >= APPLY_RESISTANT
               && ( aff->location % REVERSE_APPLY ) <= APPLY_SUSCEPTIBLE )
               {
                  value = get_flag( infoflags, ris_flags, RIS_MAX );
                  if( value < 0 || value >= RIS_MAX )
                  {
                     bug( "%s(%s): Unknown %s %s", __FUNCTION__, skill->name ? skill->name : "Unknown",
                        a_types[aff->location % REVERSE_APPLY], infoflags );
                     aff->modifier = STRALLOC( "-1" );
                     dadd = true;
                  }
                  else
                  {
                     snprintf( modifier, sizeof( modifier ), "%d", value );
                     aff->modifier = STRALLOC( modifier );
                  }
               }
               else
                  aff->modifier = STRALLOC( infoflags );

               infoflags = fread_word( fp );
               if( str_cmp( infoflags, "-1" ) )
               {
                  value = get_flag( infoflags, a_flags, AFF_MAX );
                  if( value < 0 || value >= AFF_MAX )
                  {
                     bug( "%s(%s): Unknown affect %s",
                        __FUNCTION__, skill->name ? skill->name : "Unknown", infoflags );
                     aff->bitvector = -1;
                     dadd = true;
                  }
                  else
                     aff->bitvector = value;
               }
               else
                  aff->bitvector = -1;

               if( !dadd )
                  LINK( aff, skill->first_affect, skill->last_affect, next, prev );
               else
               {
                  bug( "%s(%s): Something is bad in affect and it won't be added.",
                     __FUNCTION__, skill->name ? skill->name : "Unknown" );
                  STRFREE( aff->duration );
                  STRFREE( aff->modifier );
                  DISPOSE( aff );
               }
               fMatch = true;
               break;
            }
            break;

         case 'C':
            if ( !str_cmp( word, "Code" ) )
            {
               SPELL_FUN *spellfun;
               DO_FUN *dofun;
               char *w = fread_word( fp );

               fMatch = true;
               if( !str_prefix( "do_", w ) && ( dofun = skill_function(w) ) != skill_notfound )
               {
                  skill->skill_fun = dofun;
                  skill->spell_fun = NULL;
                  skill->skill_fun_name = STRALLOC(w);
               }
               else if( str_prefix( "do_", w ) && ( spellfun = spell_function(w) ) != spell_notfound )
               {
                  skill->spell_fun = spellfun;
                  skill->skill_fun = NULL;
                  skill->spell_fun_name = STRALLOC(w);
               }
               else
               {
                  bug( "%s(%s): unknown code %s", __FUNCTION__,
                     skill->name ? skill->name : "Unknown", w );
                  skill->spell_fun = spell_null;
               }
               break;
            }
            KEY( "Components", skill->components, fread_string( fp ) );
            break;

         case 'D':
            KEY( "Dammsg", skill->noun_damage, fread_string( fp ) );
            KEY( "Dice", skill->dice, fread_string( fp ) );
            KEY( "Diechar", skill->die_char, fread_string( fp ) );
            KEY( "Dieroom", skill->die_room, fread_string( fp ) );
            KEY( "Dievict", skill->die_vict, fread_string( fp ) );
            KEY( "Difficulty", skill->difficulty, fread_number( fp ) );
            break;

         case 'E':
            if( !str_cmp( word, "End" ) )
            {
               if( skill->saves != 0 && SPELL_SAVE( skill ) == SE_NONE )
               {
                  bug( "%s(%s):  Has saving throw (%d) with no saving effect.",
                     __FUNCTION__, skill->name ? skill->name : "Unknown", skill->saves );
                  SET_SSAV( skill, SE_NEGATE );
               }
               return skill;
            }
            break;

         case 'F':
            WEXTKEY( "Flags", skill->flags, fp, spell_flag, SF_MAX );
            break;

         case 'H':
            KEY( "Hitchar", skill->hit_char, fread_string( fp ) );
            KEY( "Hitdest", skill->hit_dest, fread_string( fp ) );
            KEY( "Hitroom", skill->hit_room, fread_string( fp ) );
            KEY( "Hitvict", skill->hit_vict, fread_string( fp ) );
            break;

         case 'I':
            KEY( "Immchar", skill->imm_char, fread_string( fp ) );
            KEY( "Immroom", skill->imm_room, fread_string( fp ) );
            KEY( "Immvict", skill->imm_vict, fread_string( fp ) );
            break;

         case 'M':
            KEY( "Mana", skill->min_mana, fread_number( fp ) );
            SKEY( "Minpos", skill->minimum_position, fp, pos_names, POS_MAX );
            KEY( "Misschar", skill->miss_char, fread_string( fp ) );
            KEY( "Missroom", skill->miss_room, fread_string( fp ) );
            KEY( "Missvict", skill->miss_vict, fread_string( fp ) );
            break;

         case 'N':
            KEY( "Name", skill->name, fread_string( fp ) );
            break;

         case 'P':
            KEY( "Participants", skill->participants, fread_number( fp ) );
            break;

         case 'R':
            KEY( "Range", skill->range, fread_number( fp ) );
            KEY( "Rounds", skill->beats, fread_number( fp ) );
            break;

         case 'S':
            SKEY( "SDamage", skill->damage, fp, spell_damage, SD_MAX );
            SKEY( "SAction", skill->action, fp, spell_action, SA_MAX );
            SKEY( "SClass", skill->Class, fp, spell_class, SC_MAX );
            SKEY( "SPower", skill->power, fp, spell_power, SP_MAX );
            SKEY( "SSave", skill->save, fp, spell_save_effect, SE_MAX );
            KEY( "Saves", skill->saves, fread_number( fp ) );
            KEY( "Slot", skill->slot, fread_number( fp ) );
            WEXTKEY( "Ssector", skill->spell_sector, fp, sect_flags, SECT_MAX );
            break;

         case 'T':
            SKEY( "Target", skill->target, fp, target_type, TAR_MAX );
            if( !str_cmp( word, "Teachers" ) )
            {
               char modifier[MSL];

               modifier[0] = '\0';
               infoflags = fread_flagstring( fp );
               while( infoflags && infoflags[0] != '\0' )
               {
                  infoflags = one_argument( infoflags, flag );
                  if( get_mob_index( atoi( flag ) ) )
                     mudstrlcat( modifier, flag, sizeof( modifier ) );
                  else
                     bug( "%s: [%s] isn't a valid teacher for skill [%s].", __FUNCTION__, flag,
                        skill->name ? skill->name : "Unknown" );
               }
               skill->teachers = STRALLOC( modifier );
               fMatch = true;
               break;
            }
            KEY( "Type", skill->type, get_skill( fread_word( fp ) ) );
            break;

         case 'V':
            KEY( "Value", skill->value, fread_number( fp ) );
            break;

         case 'W':
            KEY( "Wearoff", skill->msg_off, fread_string( fp ) );
            break;
      }

      if( !fMatch )
      {
         bug( "%s: no match: %s", __FUNCTION__, word );
         fread_to_eol( fp );
      }
   }
}

void load_skill_table( void )
{
   FILE *fp;

   if( !( fp = fopen( SKILL_FILE, "r" ) ) )
   {
      perror( SKILL_FILE );
      bug( "%s: Can't open %s", __FUNCTION__, SKILL_FILE );
      exit( 0 );
   }

   top_sn = 0;
   for( ;; )
   {
      char letter;
      char *word;

      letter = fread_letter( fp );
      if( letter == '*' )
      {
         fread_to_eol( fp );
         continue;
      }
      if( letter != '#' )
      {
         bug( "%s: (%c) found instead of a #.", __FUNCTION__, letter );
         break;
      }

      word = fread_word( fp );
      if( !str_cmp( word, "SKILL" ) )
      {
         if( top_sn >= MAX_SKILL )
         {
            bug( "%s: more skills than MAX_SKILL %d", __FUNCTION__, MAX_SKILL );
            fclose( fp );
            fp = NULL;
            return;
         }
         skill_table[top_sn++] = fread_skill( fp );
         continue;
      }
      else if( !str_cmp( word, "END" ) )
         break;
      else
      {
         bug( "%s: bad section (%s).", __FUNCTION__, word );
         fread_to_eol( fp );
         continue;
      }
   }
   fclose( fp );
   fp = NULL;
}

void load_herb_table( void )
{
   FILE *fp;

   if( !( fp = fopen( HERB_FILE, "r" ) ) )
   {
      bug( "%s: Can't open %s", __FUNCTION__, HERB_FILE );
      exit( 0 );
   }
   top_herb = 0;
   for( ;; )
   {
      char letter;
      char *word;

      letter = fread_letter( fp );
      if( letter == '*' )
      {
         fread_to_eol( fp );
         continue;
      }
      if( letter != '#' )
      {
         bug( "%s: (%c) found instead of a #.", __FUNCTION__, letter );
         break;
      }
      word = fread_word( fp );
      if( !str_cmp( word, "HERB" ) )
      {
         if( top_herb >= MAX_HERB )
         {
            bug( "%s: more herbs than MAX_HERB %d", __FUNCTION__, MAX_HERB );
            fclose( fp );
            fp = NULL;
            return;
         }
         herb_table[top_herb++] = fread_skill( fp );
         if( herb_table[top_herb - 1]->slot == 0 )
            herb_table[top_herb - 1]->slot = top_herb - 1;
         continue;
      }
      else if( !str_cmp( word, "END" ) )
         break;
      else
      {
         bug( "%s: bad section (%s).", __FUNCTION__, word );
         fread_to_eol( fp );
         continue;
      }
   }
   fclose( fp );
   fp = NULL;
}

void free_tongues( void )
{
   LANG_DATA *lang;
   LCNV_DATA *lcnv;

   while( ( lang = last_lang ) )
   {
      while( ( lcnv = lang->last_precnv ) )
      {
         UNLINK( lcnv, lang->first_precnv, lang->last_precnv, next, prev );
         STRFREE( lcnv->old );
         STRFREE( lcnv->lnew );
         DISPOSE( lcnv );
      }

      while( ( lcnv = lang->last_cnv ) )
      {
         UNLINK( lcnv, lang->first_cnv, lang->last_cnv, next, prev );
         STRFREE( lcnv->old );
         STRFREE( lcnv->lnew );
         DISPOSE( lcnv );
      }
      STRFREE( lang->name );
      STRFREE( lang->alphabet );
      UNLINK( lang, first_lang, last_lang, next, prev );
      DISPOSE( lang );
   }
}

/* Tongues / Languages loading/saving functions - Altrag */
void fread_cnv( FILE *fp, LCNV_DATA **first_cnv, LCNV_DATA **last_cnv )
{
   LCNV_DATA *cnv;
   char letter;

   for( ;; )
   {
      letter = fread_letter( fp );
      if( letter == '~' || letter == EOF )
         break;
      ungetc( letter, fp );
      CREATE( cnv, LCNV_DATA, 1 );

      cnv->old = STRALLOC( fread_word( fp ) );
      cnv->olen = strlen( cnv->old );
      cnv->lnew = STRALLOC( fread_word( fp ) );
      cnv->nlen = strlen( cnv->lnew );
      fread_to_eol( fp );
      LINK( cnv, *first_cnv, *last_cnv, next, prev );
   }
}

void load_tongues( void )
{
   FILE *fp;
   LANG_DATA *lng;
   char *word, letter;

   if( !( fp = fopen( TONGUE_FILE, "r" ) ) )
   {
      perror( TONGUE_FILE );
      return;
   }
   for( ;; )
   {
      letter = fread_letter( fp );
      if( letter == EOF )
         return;
      else if( letter == '*' )
      {
         fread_to_eol( fp );
         continue;
      }
      else if( letter != '#' )
      {
         bug( "%s: Letter '%c' not #.", __FUNCTION__, letter );
         exit( 0 );
      }
      word = fread_word( fp );
      if( !str_cmp( word, "end" ) )
         break;
      fread_to_eol( fp );
      CREATE( lng, LANG_DATA, 1 );
      lng->name = STRALLOC( word );
      fread_cnv( fp, &lng->first_precnv, &lng->last_precnv );
      lng->alphabet = fread_string( fp );
      fread_cnv( fp, &lng->first_cnv, &lng->last_cnv );
      fread_to_eol( fp );
      LINK( lng, first_lang, last_lang, next, prev );
   }
   fclose( fp );
   fp = NULL;
}

void fwrite_langs( void )
{
   FILE *fp;
   LANG_DATA *lng;
   LCNV_DATA *cnv;

   if( !( fp = fopen( TONGUE_FILE, "w" ) ) )
   {
      perror( TONGUE_FILE );
      return;
   }
   for( lng = first_lang; lng; lng = lng->next )
   {
      fprintf( fp, "#%s\n", lng->name );
      for( cnv = lng->first_precnv; cnv; cnv = cnv->next )
         fprintf( fp, "'%s' '%s'\n", cnv->old, cnv->lnew );
      fprintf( fp, "~\n%s~\n", lng->alphabet );
      for( cnv = lng->first_cnv; cnv; cnv = cnv->next )
         fprintf( fp, "'%s' '%s'\n", cnv->old, cnv->lnew );
      fprintf( fp, "\n" );
   }
   fprintf( fp, "#end\n\n" );
   fclose( fp );
   fp = NULL;
}