// // E_VerifyFilter // // Because it's exceptionally dangerous to allow the specification of format // strings by the end user, I'm going to make sure it cannot be abused. // static void E_VerifyFilter(const char *str) { const char *rover = str; bool inpct = false; bool foundpct = false; while(*rover != '\0') { if(inpct) { // formatting lengths are ok, and are indeed necessary if((*rover >= '0' && *rover <= '9') || *rover == '.') { ++rover; continue; } switch(*rover) { // allowed characters: case 'c': // char case 'i': // general int case 'd': // int case 'x': // hex case 'X': // upper-case hex case 'o': // octal case 'u': // unsigned inpct = false; break; default: // screw you, hacker boy E_EDFLoggedErr(2, "E_VerifyFilter: '%s' has bad format specifier '%c'\n", str, *rover); } } if(*rover == '%') { if(foundpct) // already found one? dirty hackers. { E_EDFLoggedErr(2, "E_VerifyFilter: '%s' has too many format specifiers\n", str); } foundpct = true; inpct = true; } ++rover; // let's not forget to do this! } }
// // E_ExtractPrefix // // Returns the result of strchr called on the string in value. // If the return is non-NULL, you can expect to find the extracted // prefix written into prefixbuf. If the return value is NULL, // prefixbuf is unmodified. // const char *E_ExtractPrefix(const char *value, char *prefixbuf, int buflen) { int i; const char *colonloc, *rover, *strval; // look for a colon ending a possible prefix colonloc = strchr(value, ':'); if(colonloc) { strval = colonloc + 1; rover = value; i = 0; // 01/10/09: initialize buffer memset(prefixbuf, 0, buflen); while(rover != colonloc && i < buflen - 1) // leave room for \0 { prefixbuf[i] = *rover; ++rover; ++i; } // check validity of the string value location (could be end) if(!(*strval)) { E_EDFLoggedErr(0, "E_ExtractPrefix: invalid prefix:value %s\n", value); } } return colonloc; }
// // E_ProcessInventoryDeltas // // Does processing for inventorydelta sections, which allow cascading editing // of existing inventory items. The inventorydelta shares most of its fields // and processing code with the thingtype section. // void E_ProcessInventoryDeltas(cfg_t *cfg) { int i, numdeltas; E_EDFLogPuts("\t* Processing inventory deltas\n"); numdeltas = cfg_size(cfg, EDF_SEC_INVDELTA); E_EDFLogPrintf("\t\t%d inventorydelta(s) defined\n", numdeltas); for(i = 0; i < numdeltas; i++) { cfg_t *deltasec = cfg_getnsec(cfg, EDF_SEC_INVDELTA, i); inventory_t *inv; // get thingtype to edit if(!cfg_size(deltasec, ITEM_DELTA_NAME)) E_EDFLoggedErr(2, "E_ProcessInventoryDeltas: inventorydelta requires name field\n"); inv = E_GetInventoryForName(cfg_getstr(deltasec, ITEM_DELTA_NAME)); E_ProcessInventory(inv, deltasec, cfg, false); E_EDFLogPrintf("\t\tApplied inventorydelta #%d to %s(#%d)\n", i, inv->name, inv->numkey); } }
// // E_ProcessSpriteVars // // Sets the sprite numbers to be used for the player and blank // sprites by looking up a provided name in the sprite hash // table. // static void E_ProcessSpriteVars(cfg_t *cfg) { static bool firsttime = true; int sprnum; const char *str; E_EDFLogPuts("\t* Processing sprite variables\n"); // haleyjd 11/11/09: removed processing of obsolete playersprite variable // haleyjd 11/21/11: on subsequent runs, only replace if changed if(!firsttime && cfg_size(cfg, ITEM_BLANKSPRITE) == 0) return; firsttime = false; // load blank sprite number str = cfg_getstr(cfg, ITEM_BLANKSPRITE); sprnum = E_SpriteNumForName(str); if(sprnum == -1) { E_EDFLoggedErr(2, "E_ProcessSpriteVars: invalid blank sprite name: '%s'\n", str); } E_EDFLogPrintf("\t\tSet sprite %s(#%d) as blank sprite\n", str, sprnum); blankSpriteNum = sprnum; }
// // E_ParseLumpRecursive // // haleyjd 03/21/10: helper routine for E_ParseEDFLump. // Recursively descends the lump hash chain. // static void E_ParseLumpRecursive(cfg_t *cfg, const char *name, int ln) { if(ln >= 0) // terminal case - lumpnum is -1 { lumpinfo_t **lumpinfo = wGlobalDir.getLumpInfo(); // recurse on next item E_ParseLumpRecursive(cfg, name, lumpinfo[ln]->namehash.next); // handle this lump if(!strncasecmp(lumpinfo[ln]->name, name, 8) && // name match lumpinfo[ln]->li_namespace == lumpinfo_t::ns_global) // is global { int err; // try to parse it if((err = cfg_parselump(cfg, name, ln))) { E_EDFLoggedErr(1, "E_ParseEDFLump: failed to parse EDF lump %s (#%d, code %d)\n", name, ln, err); } } } }
// // E_CreateString // // Creates an EDF string object with the given value which is hashable // by one or two different keys. The mnemonic key is required and must // be 128 or fewer characters long. The numeric key is optional. If a // negative value is passed as the numeric key, the object will not be // added to the numeric hash table. // edf_string_t *E_CreateString(const char *value, const char *key, int num) { int keyval; edf_string_t *newStr; if((newStr = E_StringForName(key))) { // Modify existing object. E_ReplaceString(newStr->string, estrdup(value)); // Modify numeric id and rehash object if necessary if(num != newStr->numkey) { // If old key is >= 0, must remove from hash first if(newStr->numkey >= 0) E_DelStringFromNumHash(newStr); // Set new key newStr->numkey = num; // If new key >= 0, add back to hash if(newStr->numkey >= 0) E_AddStringToNumHash(newStr); } } else { // Create a new string object newStr = estructalloc(edf_string_t, 1); // copy keys into string object if(strlen(key) >= sizeof(newStr->key)) { E_EDFLoggedErr(2, "E_CreateString: invalid string mnemonic '%s'\n", key); } strncpy(newStr->key, key, sizeof(newStr->key)); newStr->numkey = num; // duplicate value newStr->string = estrdup(value); // add to hash tables keyval = D_HashTableKey(newStr->key) % NUM_EDFSTR_CHAINS; newStr->next = edf_str_chains[keyval]; edf_str_chains[keyval] = newStr; // numeric key is not required if(num >= 0) E_AddStringToNumHash(newStr); } return newStr; }
// // E_AddInventoryToPStack // // Adds an inventory definition to the inheritance stack. // static void E_AddInventoryToPStack(inventory_t *inv) { // Overflow shouldn't happen since it would require cyclic inheritance as // well, but I'll guard against it anyways. if(inv_pindex >= numInventoryDefs) E_EDFLoggedErr(2, "E_AddInventoryToPStack: max inheritance depth exceeded\n"); inv_pstack[inv_pindex++] = inv; }
// // E_ParseEDFFile // // Parses the specified file. // static void E_ParseEDFFile(cfg_t *cfg, const char *filename) { int err; if((err = cfg_parse(cfg, filename))) { E_EDFLoggedErr(1, "E_ParseEDFFile: failed to parse %s (code %d)\n", filename, err); } }
// // E_ParseEDFLump // // Parses the specified lump. All lumps of the given name will be parsed // recursively from oldest to newest. // static void E_ParseEDFLump(cfg_t *cfg, const char *lumpname) { lumpinfo_t *root; // make sure there is at least one lump before starting recursive parsing if(W_CheckNumForName(lumpname) < 0) E_EDFLoggedErr(1, "E_ParseEDFLump: lump %s not found\n", lumpname); // get root of the hash chain for the indicated name root = wGlobalDir.getLumpNameChain(lumpname); // parse all lumps of this name recursively in last-to-first order E_ParseLumpRecursive(cfg, lumpname, root->namehash.index); }
// // E_ProcessInventory // // Generalized code to process the data for a single inventory definition // structure. Doubles as code for inventory and inventorydelta. // static void E_ProcessInventory(inventory_t *inv, cfg_t *invsec, cfg_t *pcfg, bool def) { //int tempint; const char *tempstr; bool inherits = false; // possible when inheriting from an inventory def of a previous EDF generation if(!invsec) return; // Process inheritance (not in deltas) if(def) { // if this inventory is already processed via recursion due to // inheritance, don't process it again if(inv->processed) return; if(cfg_size(invsec, ITEM_INVENTORY_INHERITS) > 0) { cfg_t *parent_invsec; // resolve parent inventory def inventory_t *parent = E_GetInventoryForName(cfg_getstr(invsec, ITEM_INVENTORY_INHERITS)); // check against cyclic inheritance if(!E_CheckInventoryInherit(parent)) { E_EDFLoggedErr(2, "E_ProcessInventory: cyclic inheritance detected in inventory '%s'\n", inv->name); } // add to inheritance stack E_AddInventoryToPStack(parent); // process parent recursively parent_invsec = cfg_gettsec(pcfg, EDF_SEC_INVENTORY, parent->name); E_ProcessInventory(parent, parent_invsec, pcfg, true); // copy parent to this thing E_CopyInventory(inv, parent); // keep track of parent explicitly inv->parent = parent; // we inherit, so treat defaults as no value inherits = true; } else inv->parent = NULL; // no parent. // mark this inventory def as processed inv->processed = true; } // field processing if(IS_SET(ITEM_INVENTORY_CLASS)) { const char *classname = cfg_getstr(invsec, ITEM_INVENTORY_CLASS); int classtype = E_StrToNumLinear(inventoryClassNames, INV_CLASS_NUMCLASSES, classname); if(classtype == INV_CLASS_NUMCLASSES) { E_EDFLoggedWarning(2, "Warning: unknown inventory class %s\n", classname); classtype = INV_CLASS_NONE; } inv->classtype = classtype; } #ifdef RANGECHECK if(inv->classtype < 0 || inv->classtype >= INV_CLASS_NUMCLASSES) E_EDFLoggedErr(2, "E_ProcessInventory: internal error - bad classtype %d\n", inv->classtype); #endif // process subclass fields inventorySubClasses[inv->classtype](inv, invsec, def, inherits); if(IS_SET(ITEM_INVENTORY_AMOUNT)) inv->amount = cfg_getint(invsec, ITEM_INVENTORY_AMOUNT); if(IS_SET(ITEM_INVENTORY_MAXAMOUNT)) inv->maxAmount = cfg_getint(invsec, ITEM_INVENTORY_MAXAMOUNT); if(IS_SET(ITEM_INVENTORY_INTERHUBAMOUNT)) inv->interHubAmount = cfg_getint(invsec, ITEM_INVENTORY_INTERHUBAMOUNT); if(IS_SET(ITEM_INVENTORY_ICON)) E_ReplaceString(inv->icon, cfg_getstrdup(invsec, ITEM_INVENTORY_ICON)); if(IS_SET(ITEM_INVENTORY_PICKUPMESSAGE)) E_ReplaceString(inv->pickUpMessage, cfg_getstrdup(invsec, ITEM_INVENTORY_PICKUPMESSAGE)); if(IS_SET(ITEM_INVENTORY_PICKUPSOUND)) E_ReplaceString(inv->pickUpSound, cfg_getstrdup(invsec, ITEM_INVENTORY_PICKUPSOUND)); if(IS_SET(ITEM_INVENTORY_PICKUPFLASH)) E_ReplaceString(inv->pickUpFlash, cfg_getstrdup(invsec, ITEM_INVENTORY_PICKUPFLASH)); if(IS_SET(ITEM_INVENTORY_USESOUND)) E_ReplaceString(inv->useSound, cfg_getstrdup(invsec, ITEM_INVENTORY_USESOUND)); if(IS_SET(ITEM_INVENTORY_RESPAWNTICS)) inv->respawnTics = cfg_getint(invsec, ITEM_INVENTORY_RESPAWNTICS); if(IS_SET(ITEM_INVENTORY_GIVEQUEST)) inv->giveQuest = cfg_getint(invsec, ITEM_INVENTORY_GIVEQUEST); if(IS_SET(ITEM_INVENTORY_FLAGS)) { tempstr = cfg_getstr(invsec, ITEM_INVENTORY_FLAGS); if(*tempstr == '\0') inv->flags = 0; else inv->flags = E_ParseFlags(tempstr, &inventory_flagset); } // TODO: addflags/remflags }
// // E_ProcessBossTypes // // Gets the thing type entries in the boss_spawner_types list, // for use by the SpawnFly codepointer. // // modified by schepe to remove 11-type limit // static void E_ProcessBossTypes(cfg_t *cfg) { int i, a = 0; int numTypes = cfg_size(cfg, SEC_BOSSTYPES); int numProbs = cfg_size(cfg, SEC_BOSSPROBS); bool useProbs = true; E_EDFLogPuts("\t* Processing boss spawn types\n"); if(!numTypes) { // haleyjd 05/31/06: allow zero boss types E_EDFLogPuts("\t\tNo boss types defined\n"); return; } // haleyjd 11/19/03: allow defaults for boss spawn probs if(!numProbs) useProbs = false; if(useProbs ? numTypes != numProbs : numTypes != 11) { E_EDFLoggedErr(2, "E_ProcessBossTypes: %d boss types, %d boss probs\n", numTypes, useProbs ? numProbs : 11); } // haleyjd 11/21/11: allow multiple runs if(BossSpawnTypes) { efree(BossSpawnTypes); BossSpawnTypes = NULL; } if(BossSpawnProbs) { efree(BossSpawnProbs); BossSpawnProbs = NULL; } NumBossTypes = numTypes; BossSpawnTypes = ecalloc(int *, numTypes, sizeof(int)); BossSpawnProbs = ecalloc(int *, numTypes, sizeof(int)); // load boss spawn probabilities for(i = 0; i < numTypes; ++i) { if(useProbs) { a += cfg_getnint(cfg, SEC_BOSSPROBS, i); BossSpawnProbs[i] = a; } else BossSpawnProbs[i] = BossDefaults[i]; } // check that the probabilities total 256 if(useProbs && a != 256) { E_EDFLoggedErr(2, "E_ProcessBossTypes: boss spawn probs do not total 256\n"); } for(i = 0; i < numTypes; ++i) { const char *typeName = cfg_getnstr(cfg, SEC_BOSSTYPES, i); int typeNum = E_ThingNumForName(typeName); if(typeNum == -1) { E_EDFLoggedWarning(2, "Warning: invalid boss type '%s'\n", typeName); typeNum = UnknownThingType; } BossSpawnTypes[i] = typeNum; E_EDFLogPrintf("\t\tAssigned type %s(#%d) to boss type %d\n", mobjinfo[typeNum]->name, typeNum, i); } }
// // E_ProcessCast // // Creates the DOOM II cast call information // static void E_ProcessCast(cfg_t *cfg) { static bool firsttime = true; int i, numcastorder = 0, numcastsections = 0; cfg_t **ci_order; E_EDFLogPuts("\t* Processing cast call\n"); // get number of cast sections numcastsections = cfg_size(cfg, SEC_CAST); if(firsttime && !numcastsections) // on main parse, at least one is required. E_EDFLoggedErr(2, "E_ProcessCast: no cast members defined.\n"); firsttime = false; E_EDFLogPrintf("\t\t%d cast member(s) defined\n", numcastsections); // haleyjd 11/21/11: allow multiple runs if(castorder) { int i; // free names for(i = 0; i < max_castorder; i++) { if(castorder[i].name) efree(castorder[i].name); } // free castorder efree(castorder); castorder = NULL; max_castorder = 0; } // check if the "castorder" array is defined for imposing an // order on the castinfo sections numcastorder = cfg_size(cfg, SEC_CASTORDER); E_EDFLogPrintf("\t\t%d cast member(s) in castorder\n", numcastorder); // determine size of castorder max_castorder = (numcastorder > 0) ? numcastorder : numcastsections; // allocate with size+1 for an end marker castorder = estructalloc(castinfo_t, max_castorder + 1); ci_order = ecalloc(cfg_t **, sizeof(cfg_t *), max_castorder); if(numcastorder > 0) { for(i = 0; i < numcastorder; ++i) { const char *title = cfg_getnstr(cfg, SEC_CASTORDER, i); cfg_t *section = cfg_gettsec(cfg, SEC_CAST, title); if(!section) { E_EDFLoggedErr(2, "E_ProcessCast: unknown cast member '%s' in castorder\n", title); } ci_order[i] = section; } } else { // no castorder array is defined, so use the cast members // in the order they are encountered (for backward compatibility) for(i = 0; i < numcastsections; ++i) ci_order[i] = cfg_getnsec(cfg, SEC_CAST, i); } for(i = 0; i < max_castorder; ++i) { int j; const char *tempstr; int tempint = 0; cfg_t *castsec = ci_order[i]; // resolve thing type tempstr = cfg_getstr(castsec, ITEM_CAST_TYPE); if(!tempstr || (tempint = E_ThingNumForName(tempstr)) == -1) { E_EDFLoggedWarning(2, "Warning: cast %d: unknown thing type %s\n", i, tempstr); tempint = UnknownThingType; } castorder[i].type = tempint; // get cast name, if any -- the first seventeen entries can // default to using the internal string editable via BEX strings tempstr = cfg_getstr(castsec, ITEM_CAST_NAME); if(cfg_size(castsec, ITEM_CAST_NAME) == 0 && i < 17) castorder[i].name = NULL; // set from DeHackEd else castorder[i].name = estrdup(tempstr); // store provided value // get stopattack flag (used by player) castorder[i].stopattack = cfg_getbool(castsec, ITEM_CAST_SA); // process sound blocks (up to four will be processed) tempint = cfg_size(castsec, ITEM_CAST_SOUND); for(j = 0; j < 4; ++j) { castorder[i].sounds[j].frame = 0; castorder[i].sounds[j].sound = 0; } for(j = 0; j < tempint && j < 4; ++j) { int num; sfxinfo_t *sfx; const char *name; cfg_t *soundsec = cfg_getnsec(castsec, ITEM_CAST_SOUND, j); // if these are invalid, just fill them with zero rather // than causing an error, as they're very unimportant // name of sound to play name = cfg_getstr(soundsec, ITEM_CAST_SOUNDNAME); // haleyjd 03/22/06: modified to support dehnum auto-allocation if((sfx = E_EDFSoundForName(name)) == NULL) { E_EDFLoggedWarning(2, "Warning: cast member references invalid sound %s\n", name); castorder[i].sounds[j].sound = 0; } else { if(sfx->dehackednum != -1 || E_AutoAllocSoundDEHNum(sfx)) castorder[i].sounds[j].sound = sfx->dehackednum; else { E_EDFLoggedWarning(2, "Warning: could not auto-allocate a DeHackEd number " "for sound %s\n", name); castorder[i].sounds[j].sound = 0; } } // name of frame that triggers sound event name = cfg_getstr(soundsec, ITEM_CAST_SOUNDFRAME); if((num = E_StateNumForName(name)) < 0) num = 0; castorder[i].sounds[j].frame = num; } } // initialize the end marker to all zeroes memset(&castorder[max_castorder], 0, sizeof(castinfo_t)); // free the ci_order table efree(ci_order); }