/***************************************************************************** * 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; }