diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 532eb14ab..b20c06e79 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,5 @@ March 22, 2009 (Changes by Graf Zahl) +- removed gamemission variable because it wasn't used anywhere. - removed gamemode variable. All it was used for were some checks that really should depend on GI_MAPxx. - Externalized all internal gameinfo definitions. diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp new file mode 100644 index 000000000..87dd45c71 --- /dev/null +++ b/src/d_iwad.cpp @@ -0,0 +1,691 @@ +/* +** d_iwad.cpp +** IWAD detection code +** +**--------------------------------------------------------------------------- +** Copyright 1998-2009 Randy Heit +** Copyright 2009 CHristoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ +#include "d_main.h" +#include "gi.h" +#include "cmdlib.h" +#include "doomstat.h" +#include "i_system.h" +#include "w_wad.h" +#include "w_zip.h" +#include "v_palette.h" +#include "m_argv.h" +#include "c_cvars.h" +#include "gameconfigfile.h" + + +CVAR (Bool, queryiwad, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); +CVAR (String, defaultiwad, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); + +// If autoname is NULL, that's either because that game doesn't allow +// loading of external wads or because it's already caught by the +// general game-specific wads section. +const IWADInfo IWADInfos[NUM_IWAD_TYPES] = +{ + // banner text, autoname, fg color, bg color + { "Final Doom: TNT - Evilution", "TNT", MAKERGB(168,0,0), MAKERGB(168,168,168), GAME_Doom, "mapinfo/tnt.txt", GI_MAPxx }, + { "Final Doom: Plutonia Experiment", "Plutonia", MAKERGB(168,0,0), MAKERGB(168,168,168), GAME_Doom, "mapinfo/plutonia.txt", GI_MAPxx }, + { "Hexen: Beyond Heretic", NULL, MAKERGB(240,240,240), MAKERGB(107,44,24), GAME_Hexen, "mapinfo/hexen.txt", GI_MAPxx }, + { "Hexen: Deathkings of the Dark Citadel", "HexenDK", MAKERGB(240,240,240), MAKERGB(139,68,9), GAME_Hexen, "mapinfo/hexen.txt", GI_MAPxx }, + { "Hexen: Demo Version", "HexenDemo",MAKERGB(240,240,240), MAKERGB(107,44,24), GAME_Hexen, "mapinfo/hexen.txt", GI_MAPxx | GI_SHAREWARE }, + { "DOOM 2: Hell on Earth", "Doom2", MAKERGB(168,0,0), MAKERGB(168,168,168), GAME_Doom, "mapinfo/doom2.txt", GI_MAPxx }, + { "Heretic Shareware", NULL, MAKERGB(252,252,0), MAKERGB(168,0,0), GAME_Heretic, "mapinfo/hereticsw.txt",GI_SHAREWARE }, + { "Heretic: Shadow of the Serpent Riders", NULL, MAKERGB(252,252,0), MAKERGB(168,0,0), GAME_Heretic, "mapinfo/heretic.txt", GI_MENUHACK_EXTENDED }, + { "Heretic", NULL, MAKERGB(252,252,0), MAKERGB(168,0,0), GAME_Heretic, "mapinfo/heretic.txt" }, + { "DOOM Shareware", NULL, MAKERGB(168,0,0), MAKERGB(168,168,168), GAME_Doom, "mapinfo/doom1.txt", GI_SHAREWARE }, + { "The Ultimate DOOM", "Doom1", MAKERGB(84,84,84), MAKERGB(168,168,168), GAME_Doom, "mapinfo/ultdoom.txt" }, + { "DOOM Registered", "Doom1", MAKERGB(84,84,84), MAKERGB(168,168,168), GAME_Doom, "mapinfo/doom1.txt" }, + { "Strife: Quest for the Sigil", NULL, MAKERGB(224,173,153), MAKERGB(0,107,101), GAME_Strife, "mapinfo/strife.txt", GI_MAPxx }, + { "Strife: Teaser (Old Version)", NULL, MAKERGB(224,173,153), MAKERGB(0,107,101), GAME_Strife, "mapinfo/strife.txt", GI_MAPxx | GI_SHAREWARE }, + { "Strife: Teaser (New Version)", NULL, MAKERGB(224,173,153), MAKERGB(0,107,101), GAME_Strife, "mapinfo/strife.txt", GI_MAPxx | GI_SHAREWARE | GI_TEASER2 }, + { "Freedoom", "Freedoom", MAKERGB(50,84,67), MAKERGB(198,220,209), GAME_Doom, "mapinfo/doom2.txt", GI_MAPxx }, + { "Freedoom \"Demo\"", "Freedoom1",MAKERGB(50,84,67), MAKERGB(198,220,209), GAME_Doom, "mapinfo/doom1.txt" }, + { "FreeDM", "FreeDM", MAKERGB(50,84,67), MAKERGB(198,220,209), GAME_Doom, "mapinfo/doom2.txt", GI_MAPxx }, + { "Chex(R) Quest", "Chex1", MAKERGB(255,255,0), MAKERGB(0,192,0), GAME_Chex, "mapinfo/chex.txt" }, + { "Chex(R) Quest 3", "Chex3", MAKERGB(255,255,0), MAKERGB(0,192,0), GAME_Chex, "mapinfo/chex3.txt" }, + //{ "ZDoom Engine", NULL, MAKERGB(168,0,0), MAKERGB(168,168,168) }, +}; + +static const char *IWADNames[] = +{ + NULL, + "doom2f.wad", + "doom2.wad", + "plutonia.wad", + "tnt.wad", + "doomu.wad", // Hack from original Linux version. Not necessary, but I threw it in anyway. + "doom.wad", + "doom1.wad", + "heretic.wad", + "heretic1.wad", + "hexen.wad", + "hexdd.wad", + "hexendemo.wad", + "hexdemo.wad", + "strife1.wad", + "strife0.wad", + "freedoom.wad", // Freedoom.wad is distributed as Doom2.wad, but this allows to have both in the same directory. + "freedoom1.wad", + "freedm.wad", + "chex.wad", + "chex3.wad", +#ifdef unix + "DOOM2.WAD", // Also look for all-uppercase names + "PLUTONIA.WAD", + "TNT.WAD", + "DOOM.WAD", + "DOOM1.WAD", + "HERETIC.WAD", + "HERETIC1.WAD", + "HEXEN.WAD", + "HEXDD.WAD", + "HEXENDEMO.WAD", + "HEXDEMO.WAD", + "STRIFE1.WAD", + "STRIFE0.WAD", + "FREEDOOM.WAD", + "FREEDOOM1.WAD", + "FREEDM.WAD", + "CHEX.WAD", + "CHEX3.WAD", +#endif + NULL +}; + +//========================================================================== +// +// ScanIWAD +// +// Scan the contents of an IWAD to determine which one it is +//========================================================================== + +static EIWADType ScanIWAD (const char *iwad) +{ + static const char checklumps[][8] = + { + "E1M1", + "E4M2", + "MAP01", + "MAP40", + "MAP60", + "TITLE", + "REDTNT2", + "CAMO1", + { 'E','X','T','E','N','D','E','D'}, + "ENDSTRF", + "MAP33", + "INVCURS", + { 'F','R','E','E','D','O','O','M' }, + "W94_1", + { 'P','O','S','S','H','0','M','0' }, + "CYCLA1", + "FLMBA1", + "MAPINFO", + { 'G','A','M','E','I','N','F','O' }, + "E2M1","E2M2","E2M3","E2M4","E2M5","E2M6","E2M7","E2M8","E2M9", + "E3M1","E3M2","E3M3","E3M4","E3M5","E3M6","E3M7","E3M8","E3M9", + "DPHOOF","BFGGA0","HEADA1","CYBRA1", + { 'S','P','I','D','A','1','D','1' }, + + }; +#define NUM_CHECKLUMPS (sizeof(checklumps)/8) + enum + { + Check_e1m1, + Check_e4m1, + Check_map01, + Check_map40, + Check_map60, + Check_title, + Check_redtnt2, + Check_cam01, + Check_Extended, + Check_endstrf, + Check_map33, + Check_invcurs, + Check_FreeDoom, + Check_W94_1, + Check_POSSH0M0, + Check_Cycla1, + Check_Flmba1, + Check_Mapinfo, + Check_Gameinfo, + Check_e2m1 + }; + int lumpsfound[NUM_CHECKLUMPS]; + size_t i; + wadinfo_t header; + FILE *f; + + memset (lumpsfound, 0, sizeof(lumpsfound)); + if ( (f = fopen (iwad, "rb")) ) + { + fread (&header, sizeof(header), 1, f); + if (header.Magic == IWAD_ID || header.Magic == PWAD_ID) + { + header.NumLumps = LittleLong(header.NumLumps); + if (0 == fseek (f, LittleLong(header.InfoTableOfs), SEEK_SET)) + { + for (i = 0; i < (size_t)header.NumLumps; i++) + { + wadlump_t lump; + size_t j; + + if (0 == fread (&lump, sizeof(lump), 1, f)) + break; + for (j = 0; j < NUM_CHECKLUMPS; j++) + if (strnicmp (lump.Name, checklumps[j], 8) == 0) + lumpsfound[j]++; + } + } + } +#if 0 + else if (header.Magic == ZIP_ID) + { // Using a zip as an IWAD replacement requires that the key lumps be in the global scope. + // This is because most of them will be "Custom IWADs" so all that we really should need + // to find is GAMEINFO, but why limit ourselves? + header.NumLumps = 0; + FileReader *reader = new FileReader(f); + DWORD centraldir = Zip_FindCentralDir(reader); + delete reader; + if (fseek(f, centraldir, SEEK_SET) == 0) + { + // First locate directory + FZipEndOfCentralDirectory directory; + if (0 != fread(&directory, sizeof(directory), 1, f) && LittleLong(directory.Magic) == 0x06054b50) + { + header.NumLumps += LittleLong(directory.NumEntries); + if (fseek(f, LittleLong(directory.DirectoryOffset), SEEK_SET) == 0) + { + // Scan directory for lumps in the global scope with key names. + do + { + FZipCentralDirectoryInfo entry; + if (0 == fread(&entry, sizeof(entry), 1, f)) + break; + if (LittleLong(entry.Magic) == 0x02014b50) + { + // Now determine the lump's short name + char* fullname = new char[LittleLong(entry.NameLength)]; + if (0 == fread(fullname, LittleLong(entry.NameLength), 1, f)) + { + delete[] fullname; + break; + } + FString name = fullname; + delete[] fullname; + + if(name.LastIndexOf('/') != -1) + continue; + name.Truncate(name.LastIndexOf('.')); + + for (size_t j = 0; j < NUM_CHECKLUMPS; j++) + if (strnicmp (name, checklumps[j], 8) == 0) + lumpsfound[j]++; + } + else + break; + } + while(true); + } + } + } + } +#endif + fclose (f); + } + + // Always check for custom iwads first. +#if 0 + if (lumpsfound[Check_Gameinfo]) + { + return IWAD_Custom; + } +#endif + if (lumpsfound[Check_title] && lumpsfound[Check_map60]) + { + return IWAD_HexenDK; + } + else if (lumpsfound[Check_map33] && lumpsfound[Check_endstrf]) + { + if (lumpsfound[Check_map01]) + { + return IWAD_Strife; + } + else if (lumpsfound[Check_invcurs]) + { + return IWAD_StrifeTeaser2; + } + else + { + return IWAD_StrifeTeaser; + } + } + else if (lumpsfound[Check_map01]) + { + if (lumpsfound[Check_FreeDoom]) + { + // Is there a 100% reliable way to tell FreeDoom and FreeDM + // apart based solely on the lump names? + if (strstr(iwad, "freedm.wad") || strstr(iwad, "FREEDM.WAD")) + { + return IWAD_FreeDM; + } + else + { + return IWAD_FreeDoom; + } + } + else if (lumpsfound[Check_redtnt2]) + { + return IWAD_Doom2TNT; + } + else if (lumpsfound[Check_cam01]) + { + return IWAD_Doom2Plutonia; + } + else + { + if (lumpsfound[Check_title]) + { + if (lumpsfound[Check_map40]) + { + return IWAD_Hexen; + } + else + { + return IWAD_HexenDemo; + } + } + else + { + return IWAD_Doom2; + } + } + } + else if (lumpsfound[Check_e1m1]) + { + if (lumpsfound[Check_title]) + { + if (!lumpsfound[Check_e2m1]) + { + return IWAD_HereticShareware; + } + else + { + if (lumpsfound[Check_Extended]) + { + return IWAD_HereticExtended; + } + else + { + return IWAD_Heretic; + } + } + } + else if (lumpsfound[Check_Cycla1] && lumpsfound[Check_Flmba1]) + { + if (!lumpsfound[Check_Mapinfo]) + { + // The original release won't work without its hacked custom EXE. + //I_FatalError("Found an incompatible version of Chex Quest 3"); + return NUM_IWAD_TYPES; // Can't use it. + } + return IWAD_ChexQuest3; + } + else + { + if (lumpsfound[Check_FreeDoom]) + { + return IWAD_FreeDoom1; + } + for (i = Check_e2m1; i < NUM_CHECKLUMPS; i++) + { + if (!lumpsfound[i]) + { + return IWAD_DoomShareware; + } + } + if (i == NUM_CHECKLUMPS) + { + if (lumpsfound[Check_e4m1]) + { + if (lumpsfound[Check_W94_1] && lumpsfound[Check_POSSH0M0]) + { + return IWAD_ChexQuest; + } + else + { + return IWAD_UltimateDoom; + } + } + else + { + return IWAD_DoomRegistered; + } + } + } + } + return NUM_IWAD_TYPES; // Don't know +} + +//========================================================================== +// +// CheckIWAD +// +// Tries to find an IWAD from a set of known IWAD names, and checks the +// contents of each one found to determine which game it belongs to. +// Returns the number of new wads found in this pass (does not count wads +// found from a previous call). +// +//========================================================================== + +static int CheckIWAD (const char *doomwaddir, WadStuff *wads) +{ + const char *slash; + int i; + int numfound; + + numfound = 0; + + slash = (doomwaddir[0] && doomwaddir[strlen (doomwaddir)-1] != '/') ? "/" : ""; + + // Search for a pre-defined IWAD + for (i = IWADNames[0] ? 0 : 1; IWADNames[i]; i++) + { + if (wads[i].Path.IsEmpty()) + { + FString iwad; + + iwad.Format ("%s%s%s", doomwaddir, slash, IWADNames[i]); + FixPathSeperator (iwad.LockBuffer()); + iwad.UnlockBuffer(); + if (FileExists (iwad)) + { + wads[i].Type = ScanIWAD (iwad); + if (wads[i].Type != NUM_IWAD_TYPES) + { + wads[i].Path = iwad; + numfound++; + } + } + } + } + + return numfound; +} + +//========================================================================== +// +// CheckIWADinEnvDir +// +// Checks for an IWAD in a path that contains one or more environment +// variables. +// +//========================================================================== + +static int CheckIWADinEnvDir (const char *str, WadStuff *wads) +{ + FString expanded = ExpandEnvVars (str); + + if (!expanded.IsEmpty()) + { + char *dir = expanded.LockBuffer (); + FixPathSeperator (dir); + expanded.UnlockBuffer (); + if (expanded[expanded.Len() - 1] != '/') + { + expanded += '/'; + } + return CheckIWAD (expanded, wads); + } + return false; +} + + +//========================================================================== +// +// IdentifyVersion +// +// Tries to find an IWAD in one of four directories under DOS or Win32: +// 1. Current directory +// 2. Executable directory +// 3. $DOOMWADDIR +// 4. $HOME +// +// Under UNIX OSes, the search path is: +// 1. Current directory +// 2. $DOOMWADDIR +// 3. $HOME/.zdoom +// 4. The share directory defined at compile time (/usr/local/share/zdoom) +// +// The search path can be altered by editing the IWADSearch.Directories +// section of the config file. +// +//========================================================================== + +static EIWADType IdentifyVersion (const char *zdoom_wad) +{ + WadStuff wads[countof(IWADNames)]; + size_t foundwads[NUM_IWAD_TYPES] = { 0 }; + const char *iwadparm = Args->CheckValue ("-iwad"); + size_t numwads; + int pickwad; + size_t i; + bool iwadparmfound = false; + FString custwad; + + if (iwadparm) + { + custwad = iwadparm; + FixPathSeperator (custwad.LockBuffer()); + if (CheckIWAD (custwad, wads)) + { // -iwad parameter was a directory + iwadparm = NULL; + } + else + { + DefaultExtension (custwad, ".wad"); + iwadparm = custwad; + IWADNames[0] = iwadparm; + CheckIWAD ("", wads); + } + } + + if (iwadparm == NULL || wads[0].Path.IsEmpty()) + { + if (GameConfig->SetSection ("IWADSearch.Directories")) + { + const char *key; + const char *value; + + while (GameConfig->NextInSection (key, value)) + { + if (stricmp (key, "Path") == 0) + { + if (strchr (value, '$') != NULL) + { + CheckIWADinEnvDir (value, wads); + } +#ifdef unix + else if (*value == '~' && (*(value + 1) == 0 || *(value + 1) == '/')) + { + FString homepath = GetUserFile (*(value + 1) ? value + 2 : value + 1, true); + CheckIWAD (homepath, wads); + } +#endif + else + { + CheckIWAD (value, wads); + } + } + } + } +#ifdef _WIN32 + FString steam_path = I_GetSteamPath(); + if (steam_path.IsNotEmpty()) + { + static const char *const steam_dirs[] = + { + "doom 2/base", + "final doom/base", + "heretic shadow of the serpent riders/base", + "hexen/base", + "hexen deathkings of the dark citadel/base", + "ultimate doom/base" + }; + steam_path += "/SteamApps/common/"; + for (i = 0; i < countof(steam_dirs); ++i) + { + CheckIWAD (steam_path + steam_dirs[i], wads); + } + } +#endif + } + + if (iwadparm != NULL && !wads[0].Path.IsEmpty()) + { + iwadparmfound = true; + } + + for (i = numwads = 0; i < countof(IWADNames); i++) + { + if (!wads[i].Path.IsEmpty()) + { + if (i != numwads) + { + wads[numwads] = wads[i]; + } + foundwads[wads[numwads].Type] = numwads + 1; + numwads++; + } + } + + if (foundwads[IWAD_HexenDK] && !foundwads[IWAD_Hexen]) + { // Cannot play Hexen DK without Hexen + size_t kill = foundwads[IWAD_HexenDK]; + for (i = kill; i < numwads; ++i) + { + wads[i - 1] = wads[i]; + } + numwads--; + foundwads[IWAD_HexenDK] = 0; + for (i = 0; i < NUM_IWAD_TYPES; ++i) + { + if (foundwads[i] > kill) + { + foundwads[i]--; + } + } + } + + if (numwads == 0) + { + I_FatalError ("Cannot find a game IWAD (doom.wad, doom2.wad, heretic.wad, etc.).\n" + "Did you install ZDoom properly? You can do either of the following:\n" + "\n" + "1. Place one or more of these wads in the same directory as ZDoom.\n" + "2. Edit your zdoom-username.ini and add the directories of your iwads\n" + "to the list beneath [IWADSearch.Directories]"); + } + + pickwad = 0; + + if (!iwadparmfound && numwads > 1) + { + int defiwad = 0; + + // Locate the user's prefered IWAD, if it was found. + if (defaultiwad[0] != '\0') + { + for (i = 0; i < numwads; ++i) + { + FString basename = ExtractFileBase (wads[i].Path); + if (stricmp (basename, defaultiwad) == 0) + { + defiwad = (int)i; + break; + } + } + } + pickwad = I_PickIWad (wads, (int)numwads, queryiwad, defiwad); + if (pickwad >= 0) + { + // The newly selected IWAD becomes the new default + FString basename = ExtractFileBase (wads[pickwad].Path); + defaultiwad = basename; + } + } + + if (pickwad < 0) + exit (0); + + // zdoom.pk3 must always be the first file loaded and the IWAD second. + D_AddFile (zdoom_wad); + + if (wads[pickwad].Type == IWAD_HexenDK) + { // load hexen.wad before loading hexdd.wad + D_AddFile (wads[foundwads[IWAD_Hexen]-1].Path); + } + + D_AddFile (wads[pickwad].Path); + + if (wads[pickwad].Type == IWAD_Strife) + { // Try to load voices.wad along with strife1.wad + long lastslash = wads[pickwad].Path.LastIndexOf ('/'); + FString path; + + if (lastslash == -1) + { + path = "";// wads[pickwad].Path; + } + else + { + path = FString (wads[pickwad].Path.GetChars(), lastslash + 1); + } + path += "voices.wad"; + D_AddFile (path); + } + + return wads[pickwad].Type; +} + + +const IWADInfo *D_FindIWAD(const char *basewad) +{ + EIWADType iwadType = IdentifyVersion(basewad); + const IWADInfo *iwad_info = &IWADInfos[iwadType]; + I_SetIWADInfo(iwad_info); + return iwad_info; +} \ No newline at end of file diff --git a/src/gametype.h b/src/gametype.h new file mode 100644 index 000000000..69eeae545 --- /dev/null +++ b/src/gametype.h @@ -0,0 +1,18 @@ +#ifndef EGAMETYPE +#define EGAMETYPE +enum EGameType +{ + GAME_Any = 0, + GAME_Doom = 1, + GAME_Heretic = 2, + GAME_Hexen = 4, + GAME_Strife = 8, + GAME_Chex = 16, //Chex is basically Doom, but we need to have a different set of actors. + + GAME_Raven = GAME_Heretic|GAME_Hexen, + GAME_DoomStrife = GAME_Doom|GAME_Strife, + GAME_DoomChex = GAME_Doom|GAME_Chex, + GAME_DoomStrifeChex = GAME_Doom|GAME_Strife|GAME_Chex +}; +#endif +