/* ** 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 "m_misc.h" #include "c_cvars.h" #include "sc_man.h" #include "v_video.h" #include "gameconfigfile.h" #include "resourcefiles/resourcefile.h" #include "version.h" CVAR (Bool, queryiwad, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); CVAR (String, defaultiwad, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); //========================================================================== // // Clear check list // //========================================================================== void FIWadManager::ClearChecks() { mLumpsFound.Resize(mIWads.Size()); for(unsigned i=0;igametype = GAME_Doom; else if (sc.Compare("Heretic")) iwad->gametype = GAME_Heretic; else if (sc.Compare("Hexen")) iwad->gametype = GAME_Hexen; else if (sc.Compare("Strife")) iwad->gametype = GAME_Strife; else if (sc.Compare("Chex")) iwad->gametype = GAME_Chex; else sc.ScriptError(NULL); } else if (sc.Compare("Mapinfo")) { sc.MustGetStringName("="); sc.MustGetString(); iwad->MapInfo = sc.String; } else if (sc.Compare("Compatibility")) { sc.MustGetStringName("="); do { sc.MustGetString(); if(sc.Compare("NoTextcolor")) iwad->flags |= GI_NOTEXTCOLOR; else if(sc.Compare("Poly1")) iwad->flags |= GI_COMPATPOLY1; else if(sc.Compare("Poly2")) iwad->flags |= GI_COMPATPOLY2; else if(sc.Compare("Shareware")) iwad->flags |= GI_SHAREWARE; else if(sc.Compare("Teaser2")) iwad->flags |= GI_TEASER2; else if(sc.Compare("Extended")) iwad->flags |= GI_MENUHACK_EXTENDED; else if(sc.Compare("Shorttex")) iwad->flags |= GI_COMPATSHORTTEX; else if(sc.Compare("Stairs")) iwad->flags |= GI_COMPATSTAIRS; else sc.ScriptError(NULL); } while (sc.CheckString(",")); } else if (sc.Compare("MustContain")) { sc.MustGetStringName("="); do { sc.MustGetString(); iwad->Lumps.Push(FString(sc.String)); } while (sc.CheckString(",")); } else if (sc.Compare("BannerColors")) { sc.MustGetStringName("="); sc.MustGetString(); iwad->FgColor = V_GetColor(NULL, sc.String); sc.MustGetStringName(","); sc.MustGetString(); iwad->BkColor = V_GetColor(NULL, sc.String); } else if (sc.Compare("Load")) { sc.MustGetStringName("="); do { sc.MustGetString(); iwad->Load.Push(FString(sc.String)); } while (sc.CheckString(",")); } else if (sc.Compare("Required")) { sc.MustGetStringName("="); sc.MustGetString(); iwad->Required = sc.String; } else { sc.ScriptError("Unknown keyword '%s'", sc.String); } } if (iwad->MapInfo.IsEmpty()) { // We must at least load the minimum defaults to allow the engine to run. iwad->MapInfo = "mapinfo/mindefaults.txt"; } } else if (sc.Compare("NAMES")) { sc.MustGetStringName("{"); mIWadNames.Push(FString()); while (!sc.CheckString("}")) { sc.MustGetString(); FString wadname = sc.String; #if defined(_WIN32) || defined(__APPLE__) // Turns out Mac OS X is case insensitive. mIWadNames.Push(wadname); #else // check for lowercase, uppercased first letter and full uppercase on Linux etc. wadname.ToLower(); mIWadNames.Push(wadname); wadname.LockBuffer()[0] = toupper(wadname[0]); wadname.UnlockBuffer(); mIWadNames.Push(wadname); wadname.ToUpper(); mIWadNames.Push(wadname); #endif } } } } //========================================================================== // // Look for IWAD definition lump // //========================================================================== void FIWadManager::ParseIWadInfos(const char *fn) { FResourceFile *resfile = FResourceFile::OpenResourceFile(fn, NULL, true); if (resfile != NULL) { DWORD cnt = resfile->LumpCount(); for(int i=cnt-1; i>=0; i--) { FResourceLump *lmp = resfile->GetLump(i); if (lmp->Namespace == ns_global && !stricmp(lmp->Name, "IWADINFO")) { // Found one! ParseIWadInfo(resfile->Filename, (const char*)lmp->CacheLump(), lmp->LumpSize); break; } } delete resfile; } if (mIWadNames.Size() == 0 || mIWads.Size() == 0) { I_FatalError("No IWAD definitions found"); } } //========================================================================== // // ScanIWAD // // Scan the contents of an IWAD to determine which one it is //========================================================================== int FIWadManager::ScanIWAD (const char *iwad) { FResourceFile *iwadfile = FResourceFile::OpenResourceFile(iwad, NULL, true); if (iwadfile == NULL) iwadfile = FResourceFile::OpenDirectory(iwad, true); //mxd. A directory can also work as an IWAD if (iwadfile != NULL) { ClearChecks(); for(DWORD ii = 0; ii < iwadfile->LumpCount(); ii++) { FResourceLump *lump = iwadfile->GetLump(ii); CheckLumpName(lump->Name); if (lump->FullName.IsNotEmpty()) { if (strnicmp(lump->FullName, "maps/", 5) == 0) { FString mapname(&lump->FullName[5], strcspn(&lump->FullName[5], ".")); CheckLumpName(mapname); } } } delete iwadfile; } return GetIWadInfo(); } //========================================================================== // // 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). // //========================================================================== int FIWadManager::CheckIWAD (const char *doomwaddir, WadStuff *wads) { const char *slash; int numfound; numfound = 0; slash = (doomwaddir[0] && doomwaddir[strlen (doomwaddir)-1] != '/') ? "/" : ""; // Search for a pre-defined IWAD for (unsigned i=0; i< mIWadNames.Size(); i++) { if (mIWadNames[i].IsNotEmpty() && wads[i].Path.IsEmpty()) { FString iwad; iwad.Format ("%s%s%s", doomwaddir, slash, mIWadNames[i].GetChars()); FixPathSeperator (iwad); if (DirEntryExists(iwad)) { wads[i].Type = ScanIWAD (iwad); if (wads[i].Type != -1) { wads[i].Path = iwad; wads[i].Name = mIWads[wads[i].Type].Name; numfound++; } } } } return numfound; } //========================================================================== // // 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/.config/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 bool havepicked = false; int FIWadManager::IdentifyVersion (TArray &wadfiles, const char *iwad, const char *zdoom_wad) { TArray wads; TArray foundwads; const char *iwadparm = Args->CheckValue ("-iwad"); int pickwad; size_t numwads; size_t i; bool iwadparmfound = false; FString custwad; wads.Resize(mIWadNames.Size()); foundwads.Resize(mIWads.Size()); memset(&foundwads[0], 0, foundwads.Size() * sizeof(foundwads[0])); if (iwadparm == NULL && iwad != NULL && *iwad != 0) { iwadparm = iwad; } if (iwadparm) { custwad = iwadparm; FixPathSeperator (custwad); if (CheckIWAD (custwad, &wads[0])) { // -iwad parameter was a directory iwadparm = NULL; } else { if(FileExists(custwad)) DefaultExtension (custwad, ".wad"); //mxd. Don't treat folders as .wads iwadparm = custwad; mIWadNames[0] = custwad; CheckIWAD ("", &wads[0]); } } if (iwadparm == NULL || wads[0].Path.IsEmpty() || mIWads[wads[0].Type].Required.IsNotEmpty()) { if (GameConfig->SetSection ("IWADSearch.Directories")) { const char *key; const char *value; while (GameConfig->NextInSection (key, value)) { if (stricmp (key, "Path") == 0) { FString nice = NicePath(value); FixPathSeperator(nice); CheckIWAD(nice, &wads[0]); } } } TArray gog_paths = I_GetGogPaths(); for (i = 0; i < gog_paths.Size(); ++i) { CheckIWAD (gog_paths[i], &wads[0]); } TArray steam_path = I_GetSteamPath(); for (i = 0; i < steam_path.Size(); ++i) { CheckIWAD (steam_path[i], &wads[0]); } } if (iwadparm != NULL && !wads[0].Path.IsEmpty()) { iwadparmfound = true; } for (i = numwads = 0; i < mIWadNames.Size(); i++) { if (!wads[i].Path.IsEmpty()) { if (i != numwads) { wads[numwads] = wads[i]; } foundwads[wads[numwads].Type] = numwads + 1; numwads++; } } for (unsigned i=0; i kill) { foundwads[j]--; } } } } } if (numwads == 0) { I_FatalError ("Cannot find a game IWAD (doom.wad, doom2.wad, heretic.wad, etc.).\n" "Did you install " GAMENAME " properly? You can do either of the following:\n" "\n" #if defined(_WIN32) "1. Place one or more of these wads in the same directory as " GAMENAME ".\n" "2. Edit your " GAMENAMELOWERCASE "-username.ini and add the directories of your iwads\n" "to the list beneath [IWADSearch.Directories]"); #elif defined(__APPLE__) "1. Place one or more of these wads in ~/Library/Application Support/" GAMENAMELOWERCASE "/\n" "2. Edit your ~/Library/Preferences/" GAMENAMELOWERCASE ".ini and add the directories\n" "of your iwads to the list beneath [IWADSearch.Directories]"); #else "1. Place one or more of these wads in ~/.config/" GAMENAMELOWERCASE "/.\n" "2. Edit your ~/.config/" GAMENAMELOWERCASE "/" GAMENAMELOWERCASE ".ini and add the directories of your\n" "iwads to the list beneath [IWADSearch.Directories]"); #endif } 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; } } } if (!havepicked) // just use the first IWAD if the restart doesn't have a -iwad parameter. We cannot open the picker in fullscreen mode. { pickwad = I_PickIWad(&wads[0], (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); havepicked = true; } } // zdoom.pk3 must always be the first file loaded and the IWAD second. wadfiles.Clear(); D_AddFile (wadfiles, zdoom_wad); if (mIWads[wads[pickwad].Type].preload >= 0) { D_AddFile (wadfiles, wads[foundwads[mIWads[wads[pickwad].Type].preload]-1].Path); } D_AddFile (wadfiles, wads[pickwad].Path); for (unsigned i=0; i < mIWads[wads[pickwad].Type].Load.Size(); i++) { 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 += mIWads[wads[pickwad].Type].Load[i]; D_AddFile (wadfiles, path); } return wads[pickwad].Type; } //========================================================================== // // Find an IWAD to use for this game // //========================================================================== const FIWADInfo *FIWadManager::FindIWAD(TArray &wadfiles, const char *iwad, const char *basewad) { int iwadType = IdentifyVersion(wadfiles, iwad, basewad); //gameiwad = iwadType; const FIWADInfo *iwad_info = &mIWads[iwadType]; if (DoomStartupInfo.Name.IsEmpty()) DoomStartupInfo.Name = iwad_info->Name; if (DoomStartupInfo.BkColor == 0 && DoomStartupInfo.FgColor == 0) { DoomStartupInfo.BkColor = iwad_info->BkColor; DoomStartupInfo.FgColor = iwad_info->FgColor; } I_SetIWADInfo(); return iwad_info; }