// BLANKART //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2020 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file dehacked.c /// \brief Load dehacked file and change tables and text #include "doomdef.h" #include "m_cond.h" #include "deh_soc.h" #include "deh_lua.h" #include "deh_tables.h" #include "k_hud.h" boolean gamedataadded = false; boolean titlechanged = false; boolean introchanged = false; static int dbg_line; static INT32 deh_num_warning = 0; FUNCPRINTF void deh_warning(const char *first, ...) { va_list argptr; char *buf = Z_Malloc(1000, PU_STATIC, NULL); va_start(argptr, first); vsnprintf(buf, 1000, first, argptr); // sizeof only returned 4 here. it didn't like that pointer. va_end(argptr); if(dbg_line == -1) // Not in a SOC, line number unknown. CONS_Alert(CONS_WARNING, "%s\n", buf); else CONS_Alert(CONS_WARNING, "Line %u: %s\n", dbg_line, buf); deh_num_warning++; Z_Free(buf); } void deh_strlcpy(char *dst, const char *src, size_t size, const char *warntext) { size_t len = strlen(src)+1; // Used to determine if truncation has been done if (len > size) deh_warning("%s exceeds max length of %s", warntext, sizeu1(size-1)); strlcpy(dst, src, size); } int freeslotusage[2][2] = {{0, 0}, {0, 0}}; // [S_, MT_][max, previous .wad's max] void DEH_UpdateMaxFreeslots(void) { freeslotusage[0][1] = freeslotusage[0][0]; freeslotusage[1][1] = freeslotusage[1][0]; } ATTRINLINE static FUNCINLINE unsigned char myfget_color(MYFILE *f) { char c = *f->curpos++; if (c == '^') // oh, nevermind then. return '^'; if (c >= '0' && c <= '9') return 0x80+(c-'0'); c = tolower(c); if (c >= 'a' && c <= 'f') return 0x80+10+(c-'a'); return 0x80; // Unhandled -- default to no color } ATTRINLINE static FUNCINLINE char myfget_hex(MYFILE *f) { char c = *f->curpos++, endchr = 0; if (c == '\\') // oh, nevermind then. return '\\'; if (c >= '0' && c <= '9') endchr += (c-'0') << 4; else if (c >= 'A' && c <= 'F') endchr += ((c-'A') + 10) << 4; else if (c >= 'a' && c <= 'f') endchr += ((c-'a') + 10) << 4; else // invalid. stop and return a question mark. return '?'; c = *f->curpos++; if (c >= '0' && c <= '9') endchr += (c-'0'); else if (c >= 'A' && c <= 'F') endchr += ((c-'A') + 10); else if (c >= 'a' && c <= 'f') endchr += ((c-'a') + 10); else // invalid. stop and return a question mark. return '?'; return endchr; } char *myfgets(char *buf, size_t bufsize, MYFILE *f) { size_t i = 0; if (myfeof(f)) return NULL; // we need one byte for a null terminated string bufsize--; while (i < bufsize && !myfeof(f)) { char c = *f->curpos++; if (c == '^') buf[i++] = myfget_color(f); else if (c == '\\') buf[i++] = myfget_hex(f); else if (c != '\r') buf[i++] = c; if (c == '\n') break; } buf[i] = '\0'; dbg_line++; return buf; } char *myhashfgets(char *buf, size_t bufsize, MYFILE *f) { size_t i = 0; if (myfeof(f)) return NULL; // we need one byte for a null terminated string bufsize--; while (i < bufsize && !myfeof(f)) { char c = *f->curpos++; if (c == '^') buf[i++] = myfget_color(f); else if (c == '\\') buf[i++] = myfget_hex(f); else if (c != '\r') buf[i++] = c; if (c == '\n') // Ensure debug line is right... dbg_line++; if (c == '#') { if (i > 0) // don't let i wrap past 0 i--; // don't include hash char in string break; } } if (buf[i] != '#') // don't include hash char in string i++; buf[i] = '\0'; return buf; } // Used when you do something invalid like read a bad item number // to prevent extra unnecessary errors static void ignorelines(MYFILE *f) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); do { if (myfgets(s, MAXLINELEN, f)) { if (s[0] == '\n') break; } } while (!myfeof(f)); Z_Free(s); } static void ignoremenulines(MYFILE *f) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); char *tmp; do { if (myfgets(s, MAXLINELEN, f)) { if (s[0] == '\n') break; // First remove trailing newline, if there is one tmp = strchr(s, '\n'); if (tmp) *tmp = '\0'; tmp = strchr(s, '#'); if (tmp) *tmp = '\0'; if (s == tmp) continue; // Skip comment lines, but don't break. ignorelines(f); } } while (!myfeof(f)); Z_Free(s); } void DEH_Link(const char *name, dehinfo_t *info, strbuf_t **buf) { info->namehash = HASH32(name, strlen(name)); info->nameofs = strbuf_append(buf, name); } INT32 DEH_ReadFreeslot(const char *type, const char *word, INT32 *out) { size_t i; // TODO: Check for existing freeslot mobjs/states/etc. and make errors. // TODO: Name too long (truncated) warnings. if (fastcmp(type, "SFX")) { CONS_Printf("Sound sfx_%s allocated.\n", word); i = S_AddSoundFx(word, false, 0, false); if (i != sfx_None) { *out = i; return 0; } CONS_Alert(CONS_WARNING, "Ran out of free SFX slots!\n"); return -1; } else if (fastcmp(type, "SPR")) { for (i = SPR_FIRSTFREESLOT; i <= SPR_LASTFREESLOT; i++) { if (used_spr[(i-SPR_FIRSTFREESLOT)/8] & (1<<(i%8))) continue; // Already allocated, next. // Found a free slot! CONS_Printf("Sprite SPR_%s allocated.\n",word); strncpy(sprnames[i],word,4); used_spr[(i-SPR_FIRSTFREESLOT)/8] |= 1<<(i%8); // Okay, this sprite slot has been named now. // Lua needs to update the value in _G if it exists LUA_UpdateSprName(word, i); *out = i; return 0; } CONS_Alert(CONS_WARNING, "Ran out of free sprite slots!\n"); return -1; } else if (fastcmp(type, "S")) { *out = DEH_FindState(word); if (*out != NUMSTATES) return 0; // already allocated if (numstates == NUMSTATES) { CONS_Alert(CONS_WARNING, "Ran out of free State slots!\n"); return -1; } CONS_Printf("State S_%s allocated.\n",word); DEH_Link(word, &states[numstates].info, &statenames); *out = numstates++; freeslotusage[0][0]++; return 0; } else if (fastcmp(type, "MT")) { *out = DEH_FindMobjtype(word); if (*out != NUMMOBJTYPES) return 0; // already allocated if (nummobjtypes == NUMMOBJTYPES) { CONS_Alert(CONS_WARNING, "Ran out of free MobjType slots!\n"); return -1; } CONS_Printf("MobjType MT_%s allocated.\n",word); DEH_Link(word, &mobjinfo[nummobjtypes].info, &mobjnames); *out = nummobjtypes++; freeslotusage[1][0]++; return 0; } else if (fastcmp(type, "SKINCOLOR")) { *out = DEH_FindSkincolor(word); if (*out != MAXSKINCOLORS) return 0; // already allocated if (numskincolors == MAXSKINCOLORS) { CONS_Alert(CONS_WARNING, "Ran out of free skincolor slots!\n"); return -1; } CONS_Printf("Skincolor SKINCOLOR_%s allocated.\n",word); DEH_Link(word, &skincolors[numskincolors].info, &skincolornames); M_AddMenuColor(numskincolors); K_ReloadHUDColorCvar(); *out = numskincolors++; return 0; } else if (fastcmp(type, "SPR2")) { // Search if we already have an SPR2 by that name... for (i = SPR2_FIRSTFREESLOT; i < free_spr2; i++) if (memcmp(spr2names[i],word,4) == 0) break; // We don't, so allocate a new one. if (i >= free_spr2) { if (free_spr2 < NUMPLAYERSPRITES) { CONS_Printf("Sprite SPR2_%s allocated.\n",word); strncpy(spr2names[free_spr2],word,4); spr2defaults[free_spr2] = 0; *out = free_spr2; spr2names[free_spr2++][4] = 0; return 0; } else { CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n"); return -1; } } } else if (fastcmp(type, "TOL")) { // Search if we already have a typeoflevel by that name... for (i = 0; TYPEOFLEVEL[i].name; i++) if (fastcmp(word, TYPEOFLEVEL[i].name)) break; // We don't, so allocate a new one. if (TYPEOFLEVEL[i].name == NULL) { if (lastcustomtol == (UINT32)MAXTOL) // Unless you have way too many, since they're flags. { CONS_Alert(CONS_WARNING, "Ran out of free typeoflevel slots!\n"); return -1; } else { CONS_Printf("TypeOfLevel TOL_%s allocated.\n",word); G_AddTOL(lastcustomtol, word); *out = lastcustomtol; lastcustomtol <<= 1; return 0; } } } else if (fastcmp(type, "PRECIP")) { // Search if we already have a PRECIP by that name... for (i = PRECIP_FIRSTFREESLOT; i < precip_freeslot; i++) if (fastcmp(word, precipprops[i].name)) break; // We don't, so allocate a new one. if (i >= precip_freeslot) { if (precip_freeslot < MAXPRECIP) { CONS_Printf("Weather PRECIP_%s allocated.\n",word); precipprops[i].name = Z_StrDup(word); *out = precip_freeslot; precip_freeslot++; return 0; } else { CONS_Alert(CONS_WARNING, "Ran out of free PRECIP slots!\n"); return -1; } } } else if (fastcmp(type, "MN")) { *out = DEH_FindMenutype(word); if (*out != MAXMENUTYPES) return 0; // already allocated if (nummenutypes == MAXMENUTYPES) { CONS_Alert(CONS_WARNING, "Ran out of free menu slots!\n"); return -1; } CONS_Printf("Menu MN_%s allocated.\n",word); DEH_Link(word, &menudefs[nummenutypes].info, &menunames); *out = nummenutypes++; return 0; } return -2; } static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); char textline[MAXLINELEN]; char *word; char *word2; INT32 i; deh_num_warning = 0; gamedataadded = titlechanged = introchanged = false; // it doesn't test the version of SRB2 and version of dehacked file dbg_line = -1; // start at -1 so the first line is 0. while (!myfeof(f)) { myfgets(s, MAXLINELEN, f); memcpy(textline, s, MAXLINELEN); if (s[0] == '\n' || s[0] == '#') continue; if (NULL != (word = strtok(s, " "))) { strupr(word); if (word[strlen(word)-1] == '\n') word[strlen(word)-1] = '\0'; } if (word) { if (fastcmp(word, "FREESLOT")) { // This is not a major mod. readfreeslots(f); continue; } else if (fastcmp(word, "MAINCFG")) { G_SetGameModified(multiplayer, true); readmaincfg(f); continue; } else if (fastcmp(word, "WIPES")) { // This is not a major mod. readwipes(f); continue; } // // SRB2KART // else if (fastcmp(word, "FOLLOWER")) { // This is not a major mod. readfollower(f); continue; } word2 = strtok(NULL, " "); if (word2) { strupr(word2); if (word2[strlen(word2) - 1] == '\n') word2[strlen(word2) - 1] = '\0'; i = atoi(word2); } else i = 0; if (fastcmp(word, "EMBLEM")) { if (!mainfile && !gamedataadded) { deh_warning("You must define a custom gamedata to use \"%s\"", word); ignorelines(f); } else { if (!word2) i = numemblems + 1; if (i > 0 && i <= MAXEMBLEMS) { if (numemblems < i) numemblems = i; reademblemdata(f, i); } else { deh_warning("Emblem number %d out of range (1 - %d)", i, MAXEMBLEMS); ignorelines(f); } } continue; } else if (fastcmp(word, "EXTRAEMBLEM")) { if (!mainfile && !gamedataadded) { deh_warning("You must define a custom gamedata to use \"%s\"", word); ignorelines(f); } else { if (!word2) i = numextraemblems + 1; if (i > 0 && i <= MAXEXTRAEMBLEMS) { if (numextraemblems < i) numextraemblems = i; readextraemblemdata(f, i); } else { deh_warning("Extra emblem number %d out of range (1 - %d)", i, MAXEXTRAEMBLEMS); ignorelines(f); } } continue; } if (word2) { if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT")) { if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_mobjtype(word2); // find a thing by name if (i < NUMMOBJTYPES && i >= 0) { if (!mainfile && i < (MT_FIRSTFREESLOT+freeslotusage[1][1])) { G_SetGameModified(multiplayer, true); // Only a major mod if editing stuff that isn't your own! } readthing(f, i); } else { deh_warning("Thing %d out of range (0 - %d)", i, NUMMOBJTYPES-1); ignorelines(f); } } else if (fastcmp(word, "SKINCOLOR") || fastcmp(word, "COLOR")) { if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_skincolor(word2); // find a skincolor by name if (i < numskincolors && i >= 0) readskincolor(f, i, mainfile); else { deh_warning("Skincolor %d out of range (0 - %d)", i, numskincolors-1); ignorelines(f); } } else if (fastcmp(word, "SPRITE2")) { if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_sprite2(word2); // find a sprite by name if (i < (INT32)free_spr2 && i >= (INT32)SPR2_FIRSTFREESLOT) readsprite2(f, i); else { deh_warning("Sprite2 number %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1); ignorelines(f); } } #ifdef HWRENDER else if (fastcmp(word, "LIGHT")) { // TODO: Read lights by name if (i > 0 && i < NUMLIGHTS) readlight(f, i); else { deh_warning("Light number %d out of range (1 - %d)", i, NUMLIGHTS-1); ignorelines(f); } } #endif else if (fastcmp(word, "LEVEL")) { size_t len = strlen(word2); if (len <= MAXMAPLUMPNAME-1) { readlevelheader(f, word2); } else { deh_warning("Map header's lumpname %s is too long (%s characters VS %d max)", word2, sizeu1(len), (MAXMAPLUMPNAME-1)); ignorelines(f); } } else if (fastcmp(word, "GAMETYPE")) { // Get the gametype name from textline // instead of word2, so that gametype names // aren't allcaps INT32 c; for (c = 0; c < MAXLINELEN; c++) { if (textline[c] == '\0') break; if (textline[c] == ' ') { char *gtname = (textline+c+1); if (gtname) { // remove funny characters INT32 j; for (j = 0; j < (MAXLINELEN - c); j++) { if (gtname[j] == '\0') break; if (gtname[j] < 32) gtname[j] = '\0'; } readgametype(f, gtname); } break; } } } else if (fastcmp(word, "CUTSCENE")) { if (i > 0 && i < 129) readcutscene(f, i - 1); else { deh_warning("Cutscene number %d out of range (1 - 128)", i); ignorelines(f); } } else if (fastcmp(word, "PROMPT")) { if (i > 0 && i < MAX_PROMPTS) readtextprompt(f, i - 1); else { deh_warning("Prompt number %d out of range (1 - %d)", i, MAX_PROMPTS); ignorelines(f); } } else if (fastcmp(word, "FRAME") || fastcmp(word, "STATE")) { if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_state(word2); // find a state by name if (i < NUMSTATES && i >= 0) { if (!mainfile && i < (S_FIRSTFREESLOT+freeslotusage[0][1])) { G_SetGameModified(multiplayer, true); // Only a major mod if editing stuff that isn't your own! } readframe(f, i); } else { deh_warning("Frame %d out of range (0 - %d)", i, NUMSTATES-1); ignorelines(f); } } else if (fastcmp(word, "SOUND")) { if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_sfx(word2); // find a sound by name if (i < NUMSFX && i >= 0) readsound(f, i); else { deh_warning("Sound %d out of range (0 - %d)", i, NUMSFX-1); ignorelines(f); } } else if (fastcmp(word, "MENU")) { if (dedicated) { ignoremenulines(f); continue; // dedis don't need menus, silly! } if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_menutype(word2); // find a huditem by name if (i >= 1 && i < nummenutypes) readmenu(f, i); else { // zero-based, but let's start at 1 deh_warning("Menu number %d out of range (1 - %d)", i, nummenutypes-1); ignorelines(f); } } else if (fastcmp(word, "UNLOCKABLE")) { if (!mainfile && !gamedataadded) { deh_warning("You must define a custom gamedata to use \"%s\"", word); ignorelines(f); } else if (i > 0 && i <= MAXUNLOCKABLES) readunlockable(f, i - 1); else { deh_warning("Unlockable number %d out of range (1 - %d)", i, MAXUNLOCKABLES); ignorelines(f); } } else if (fastcmp(word, "CONDITIONSET")) { if (!mainfile && !gamedataadded) { deh_warning("You must define a custom gamedata to use \"%s\"", word); ignorelines(f); } else if (i > 0 && i <= MAXCONDITIONSETS) readconditionset(f, (UINT8)i); else { deh_warning("Condition set number %d out of range (1 - %d)", i, MAXCONDITIONSETS); ignorelines(f); } } // // SRB2KART // else if (fastcmp(word, "CUP")) { size_t len = strlen(word2); if (len <= MAXCUPNAME-1) { cupheader_t *cup = kartcupheaders; cupheader_t *prev = NULL; UINT32 hash = quickncasehash(word2, MAXCUPNAME); while (cup) { if (!mainfile && hash == cup->namehash && fastcmp(cup->name, word2)) { // Only a major mod if editing stuff that isn't your own! G_SetGameModified(multiplayer, true); break; } prev = cup; cup = cup->next; } // Nothing found, add to the end. if (!cup) { cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); cup->id = numkartcupheaders; cup->monitor = 1; deh_strlcpy(cup->name, word2, sizeof(cup->name), va("Cup header %s: name", word2)); cup->namehash = hash; if (prev != NULL) prev->next = cup; if (kartcupheaders == NULL) kartcupheaders = cup; numkartcupheaders++; CONS_Printf("Added cup %d ('%s')\n", cup->id, cup->name); } readcupheader(f, cup); } else { deh_warning("Cup header's name %s is too long (%s characters VS %d max)", word2, sizeu1(len), (MAXCUPNAME-1)); ignorelines(f); } } else if (fastcmp(word, "WEATHER") || fastcmp(word, "PRECIP") || fastcmp(word, "PRECIPITATION")) { if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_precip(word2); // find a weather type by name if (i < MAXPRECIP && i > 0) readweather(f, i); else { deh_warning("Weather number %d out of range (1 - %d)", i, MAXPRECIP-1); ignorelines(f); } } else if (fastcmp(word, "SRB2KART")) { if (isdigit(word2[0])) { i = atoi(word2); if (i != PATCHVERSION) { deh_warning( "Patch is for SRB2Kart version %d, " "only version %d is supported", i, PATCHVERSION ); } } else { deh_warning( "SRB2Kart version definition has incorrect format, " "use \"SRB2KART %d\"", PATCHVERSION ); } } else if (fastcmp(word, "RINGRACERS")) { deh_warning("Patch is only compatible with Ring Racers."); } else if (fastcmp(word, "SRB2")) { deh_warning("Patch is only compatible with base SRB2."); } // Clear all data in certain locations (mostly for unlocks) // Unless you REALLY want to piss people off, // define a custom gamedata /before/ doing this!! // (then again, modifiedgame will prevent game data saving anyway) else if (fastcmp(word, "CLEAR")) { boolean clearall = (fastcmp(word2, "ALL")); if (!mainfile && !gamedataadded) { deh_warning("You must define a custom gamedata to use \"%s\"", word); continue; } if (clearall || fastcmp(word2, "UNLOCKABLES")) memset(&unlockables, 0, sizeof(unlockables)); if (clearall || fastcmp(word2, "EMBLEMS")) { memset(&emblemlocations, 0, sizeof(emblemlocations)); numemblems = 0; } if (clearall || fastcmp(word2, "EXTRAEMBLEMS")) { memset(&extraemblems, 0, sizeof(extraemblems)); numextraemblems = 0; } if (clearall || fastcmp(word2, "CONDITIONSETS")) clear_conditionsets(); if (clearall || fastcmp(word2, "LEVELS")) clear_levels(); } else deh_warning("Unknown word: %s", word); } else deh_warning("missing argument for '%s'", word); } else deh_warning("No word in this line: %s", s); } // end while if (gamedataadded) G_LoadGameData(); if (gamestate == GS_TITLESCREEN) { if (introchanged) { M_ClearMenus(true); I_UpdateMouseGrab(); COM_BufAddText("playintro"); } else if (titlechanged) { M_ClearMenus(true); I_UpdateMouseGrab(); COM_BufAddText("exitgame"); // Command_ExitGame_f() but delayed } } dbg_line = -1; if (deh_num_warning) { CONS_Printf(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s"); if (devparm) { I_Error("%s%s",va(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s"), M_GetText("See log.txt for details.\n")); //while (!I_GetKey()) //I_OsPolling(); } } Z_Free(s); } // read dehacked lump in a wad (there is special trick for for deh // file that are converted to wad in w_wad.c) void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump, boolean mainfile) { MYFILE f; f.wad = wad; f.size = W_LumpLengthPwad(wad, lump); f.data = Z_Malloc(f.size + 1, PU_STATIC, NULL); W_ReadLumpPwad(wad, lump, f.data); f.curpos = f.data; f.data[f.size] = 0; DEH_LoadDehackedFile(&f, mainfile); Z_Free(f.data); } void DEH_LoadDehackedLump(lumpnum_t lumpnum) { DEH_LoadDehackedLumpPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum), false); }