Kart-Public/src/w_wad.c
Eidolon 8f354ad9c1 Implement Uncapped (squashed)
Co-Authored-By: Sally Coolatta <tehrealsalt@gmail.com>
Co-Authored-By: James R <justsomejames2@gmail.com>
Co-Authored-By: Monster Iestyn <iestynjealous@ntlworld.com>
Co-Authored-By: katsy <katmint@live.com>

Place Frame Interpolation in "Experimental" video options header

This seems like an appropriate way to describe the feature for now.

Add smooth level platter under interpolation, `renderdeltatics`

`renderdeltatics` can be used as a standard delta time in any place,
allowing for smooth menus. It will always be equal to `realtics`
when frame interpolation is turned off, producing consistent
framerate behavior everywhere it is used.

Add smooth rendering to save select screen

Add smooth rendering to Record/NiGHTS Attack, F_SkyScroll

Ensure viewsector is accurate to viewx/viewy

This fixes a potential crash in OpenGL when changing between levels.

Ensure + commands get executed before map start

Always have precise_t defined

Fix misc dropshadow issues

Reset view interpolation on level load

Remove unnecessary precipmobj thinker hack

Add reset interpolation state functions

Reset precip interpolation on snap to ceil

Reset mobj interp state on TeleportMove

Only swap view interp state if a tick is run

Run anti-lag chasecam at tic frequency

Fixes jittery and unstable chasecam in high latency netgames

Homogenize mobj interpolations

Add sector plane level interpolations

Add SectorScroll interpolator

Add SideScroll interpolator

Add Polyobj interpolator

Intialize interpolator list at a better time

Delete interpolators associated with thinkers

Interpolate mobj angles and player drawangle

Interpolate HWR_DrawModel

Add functions to handle interpolation

Much less code duplication

P_InitAngle, to fix angle interpolation on spawning objects

Fully fix drop shadows

It used the thing's floorz / ceilingz directly -- that wouldn't account for interpolated coordinates.

Do not speed up underwater/heatwave effect in OpenGL

Closer OpenGL underwater/heatwave effect to Software

Interpolate from time of previous tic

Previously interpolated from last 35th of a second, which
may be offset from game time due to connection lag.

Consider this the proper fix to 54148a0dd0 too.

Calculate FPS stuff even if frame is skipped

I decided ultimately to actually keep the frame skip optimization disabled, because I think it is actually a little bit helpful that you can still get accurate rendering perfstats while paused, however if we decide otherwise then we can have this optimization back without making the game act like it's lagging.

Keep rect in memory

Feel better about this than creating one all da time

Lots of FPS stuff

- Disabled VSync, due to the numerous problems it has.
- Instead, added an FPS cap.
- Frame interpolation is now tied to fpscap != 35.
- By default, the FPS cap is set to the monitor's refresh rate.
- Rewrote the FPS counter.

(This also consolidates several more commits ahead of this
fixing various issues. -eid)

Misc changes after Kart cherry-picks

Fix renderdeltatics with new timing data

Update mobj oldstates before all thinkers

Allow FPS cap values

Adjust how FPS cap is checked to improve FPS stability

Fix precip crash from missing vars

Improve the framerate limiter's timing for extreme stable FPS

Handle the sleep at the end of D_SRB2Loop instead of the start

Simplifies logic in the other parts of the loop, and fixes problems with it frequently waiting too long.

Reset mobj interp state on add

Add mobj interpolator on load netgame

Move mobj interpolators to r_fps

Dynamic slope interpolators

I_GetFrameTime to try and improve frame pace

(It doesn't feel that much better though.)

Move I_FinishUpdate to D_SRB2Loop to sync screen updates with FPS cap, use timestamps in I_FrameCapSleep to simplify the code

Fix plane interpolation light level flickering

Fix flickering plane interpolation for OpenGL in the exact same way

Funny OpenGL renderer being at least 50% copy-pasted Software code :)

P_SetOrigin & P_MoveOrigin to replace P_TeleportMove

Convert P_TeleportMove use to origin funcs

Revert "P_InitAngle, to fix angle interpolation on spawning objects"

This reverts commit a80c98bd164a2748cbbfad9027b34601185d93f5.

Waypoint polyobjects interpolate z & children

Add interpolation to more moving plane types

Adds interpolation to the following:
- Crumbling platforms
- Mario blocks
- Floatbob platforms (this one works really strangely due to two thinkers, maybe double-check this one?)

Reset overlays interp states each TryRunTics

Interpolate model interpolation (lol)

Use interp tracer pos for GL linkdraw

Papersprite angle interpolation

Makes the ending signpost smooth

Move intermission emerald bounce to ticker

Bring back shadows on polyobjects

Also optimizes the method used so rings can show their shadows too. Using just the subsector is a tad bit imprecise admittedly but any more precise methods get really laggy.

Fix a bunch of ticking in hu_ drawing functions

Revert "Reset overlays interp states each TryRunTics"

This reverts commit a71a216faa20e8751b3bd0157354e8d748940c92.

Move intro ticking out of the drawer

Adjust 1up monitor icon z offsets

Fixes interpolation issues with 1up monitors.

Delta time choose player menu animations

Add drawerlib deltaTime function

Interpolate afterimages further back

Use old sleep in dedicated mode

Clamp cechotimer to 0

Fixes issues with cechos staying on-screen and glitching out
(NiGHTS items for example).

Revert "Remove unnecessary precipmobj thinker hack"

This reverts commit 0e38208620d19ec2ab690740438ac2fc7862a49e.

Fix frame pacing when game lags behind

The frame timestamp should've been made at the start of the frame, not the end.

Fix I_FrameCapSleep not respecting cpusleep

Jonathan Joestar bruh

Allow dedicated to use precise sleep timing again

Instead of only using one old sleep, just enforce framerate cap to match TICRATE.

Make Lua TeleportMove call MoveOrigin

Reset Metal fume interp state on appear

Add interpdebug

Put interpdebug stuff in perfstats instead

Add timescale cvar

Slow the game down to debug animations / interpolation problems! Speed it up if you need to get somewhere quickly while mapping!

Enable timescale outside of DEVELOP builds

It has NETVAR, so it should be fine -- put an end to useful debugging features excluded in multiplayer!

Force interpolation when timescale != 1.0

Reset old_z in MT_LOCKON think

Fixes interpolation artifacting due to spawn pos.

Fix cutscenes in interp

Fix boss1 laser in interp

Interpolate mobj scale

Precalculate refresh rate

Slower PCs can have issue querying mode over and over. This might kinda suck for windowed mode if you have different refresh rate displays but oh well

Fix interp scaling crashing software

Reset interp scale when Lua sets .scale

Disable angle interp on fresh mobjs

Fix interp scale crash for hires sprites

Interp shadow scales

Copy interp state in P_SpawnMobjFromMobj

Fix multiplayer character select

Don't interpolate mobj state if frac = 1.0

Fix Mario block item placement

Interpolate spritescale/offset x/y

Fix offset copies for SpawnMobjFromMobj

THANKS SAL

Add Lua HUD drawlists

Buffers draw calls between tics to ensure hooks
run at the originally intended rate.

Rename drawerlib deltaTime to getDeltaTime

Make renderisnewtic is false between tics

I know what I'm doing! I swear

Completely refactor timing system

Time is now tracked internally in the game using I_GetPreciseTime
and I_UpdateTime. I_Time now pulls from this internal timer. The
system code no longer needs to keep track of time itself.

This significantly improves frame and tic timing in interp mode,
resulting in a much smoother image with essentially no judder at
any framerate.

Ensure mobj interpolators reset on level load

Ensure view is not interpolated on first frame

Disable sprite offset interpolation (for now)

Refactor timing code even more

System layer is greatly simplified and framecap
logic has been moved internally. I_Sleep now
takes a sleep duration and I_SleepDuration
generically implements a precise sleep with spin
loop.
2022-05-01 17:35:30 -05:00

1744 lines
45 KiB
C

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2018 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 w_wad.c
/// \brief Handles WAD file header, directory, lump I/O
#ifdef HAVE_ZLIB
#ifndef _MSC_VER
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif
#endif
#ifndef _LFS64_LARGEFILE
#define _LFS64_LARGEFILE
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 0
#endif
#include "zlib.h"
#endif
#ifdef __GNUC__
#include <unistd.h>
#endif
#define ZWAD
#ifdef ZWAD
#ifdef _WIN32_WCE
#define AVOID_ERRNO
#else
#include <errno.h>
#endif
#include "lzf.h"
#endif
#include "doomdef.h"
#include "doomstat.h"
#include "doomtype.h"
#include "w_wad.h"
#include "z_zone.h"
#include "fastcmp.h"
#include "g_game.h" // G_LoadGameData
#include "filesrch.h"
#include "i_time.h"
#include "i_video.h" // rendermode
#include "d_netfil.h"
#include "dehacked.h"
#include "d_clisrv.h"
#include "r_defs.h"
#include "i_system.h"
#include "md5.h"
#include "lua_script.h"
#ifdef SCANTHINGS
#include "p_setup.h" // P_ScanThings
#endif
#include "m_misc.h" // M_MapNumber
#ifdef HWRENDER
#include "r_data.h"
#include "hardware/hw_main.h"
#include "hardware/hw_glob.h"
#endif
#ifdef PC_DOS
#include <stdio.h> // for snprintf
int snprintf(char *str, size_t n, const char *fmt, ...);
//int vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
typedef struct
{
const char *name;
size_t len;
} lumpchecklist_t;
// Must be a power of two
#define LUMPNUMCACHESIZE 64
typedef struct lumpnum_cache_s
{
char lumpname[8];
lumpnum_t lumpnum;
} lumpnum_cache_t;
static lumpnum_cache_t lumpnumcache[LUMPNUMCACHESIZE];
static UINT16 lumpnumcacheindex = 0;
//===========================================================================
// GLOBALS
//===========================================================================
UINT16 numwadfiles = 0; // number of active wadfiles
wadfile_t *wadfiles[MAX_WADFILES]; // 0 to numwadfiles-1 are valid
// W_Shutdown
// Closes all of the WAD files before quitting
// If not done on a Mac then open wad files
// can prevent removable media they are on from
// being ejected
void W_Shutdown(void)
{
while (numwadfiles--)
{
fclose(wadfiles[numwadfiles]->handle);
Z_Free(wadfiles[numwadfiles]->filename);
while (wadfiles[numwadfiles]->numlumps--)
Z_Free(wadfiles[numwadfiles]->lumpinfo[wadfiles[numwadfiles]->numlumps].name2);
Z_Free(wadfiles[numwadfiles]->lumpinfo);
Z_Free(wadfiles[numwadfiles]);
}
}
//===========================================================================
// LUMP BASED ROUTINES
//===========================================================================
// W_AddFile
// All files are optional, but at least one file must be
// found (PWAD, if all required lumps are present).
// Files with a .wad extension are wadlink files
// with multiple lumps.
// Other files are single lumps with the base filename
// for the lump name.
static char filenamebuf[MAX_WADPATH];
// W_OpenWadFile
// Helper function for opening the WAD file.
// Returns the FILE * handle for the file, or NULL if not found or could not be opened
// If "useerrors" is true then print errors in the console, else just don't bother
// "filename" may be modified to have the correct path the actual file is located in, if necessary
FILE *W_OpenWadFile(const char **filename, boolean useerrors)
{
FILE *handle;
if (filenamebuf != *filename) {
// avoid overlap
strncpy(filenamebuf, *filename, MAX_WADPATH);
}
filenamebuf[MAX_WADPATH - 1] = '\0';
*filename = filenamebuf;
// open wad file
if ((handle = fopen(*filename, "rb")) == NULL)
{
// If we failed to load the file with the path as specified by
// the user, strip the directories and search for the file.
nameonly(filenamebuf);
// If findfile finds the file, the full path will be returned
// in filenamebuf == *filename.
if (findfile(filenamebuf, NULL, true))
{
if ((handle = fopen(*filename, "rb")) == NULL)
{
if (useerrors)
CONS_Alert(CONS_ERROR, M_GetText("Can't open %s\n"), *filename);
return NULL;
}
}
else
{
if (useerrors)
CONS_Alert(CONS_ERROR, M_GetText("File %s not found.\n"), *filename);
return NULL;
}
}
return handle;
}
// Look for all DEHACKED and Lua scripts inside a PK3 archive.
static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum)
{
UINT16 posStart, posEnd;
#ifdef HAVE_BLUA
posStart = W_CheckNumForFolderStartPK3("Lua/", wadnum, 0);
if (posStart != INT16_MAX)
{
posEnd = W_CheckNumForFolderEndPK3("Lua/", wadnum, posStart);
posStart++;
for (; posStart < posEnd; posStart++)
LUA_LoadLump(wadnum, posStart);
}
#endif
posStart = W_CheckNumForFolderStartPK3("SOC/", wadnum, 0);
if (posStart != INT16_MAX)
{
posEnd = W_CheckNumForFolderEndPK3("SOC/", wadnum, posStart);
posStart++;
for(; posStart < posEnd; posStart++)
{
lumpinfo_t *lump_p = &wadfiles[wadnum]->lumpinfo[posStart];
size_t length = strlen(wadfiles[wadnum]->filename) + 1 + strlen(lump_p->name2); // length of file name, '|', and lump name
char *name = malloc(length + 1);
sprintf(name, "%s|%s", wadfiles[wadnum]->filename, lump_p->name2);
name[length] = '\0';
CONS_Printf(M_GetText("Loading SOC from %s\n"), name);
DEH_LoadDehackedLumpPwad(wadnum, posStart);
free(name);
}
}
}
// search for all DEHACKED lump in all wads and load it
static inline void W_LoadDehackedLumps(UINT16 wadnum)
{
UINT16 lump;
#ifdef HAVE_BLUA
// Find Lua scripts before SOCs to allow new A_Actions in SOC editing.
{
lumpinfo_t *lump_p = wadfiles[wadnum]->lumpinfo;
for (lump = 0; lump < wadfiles[wadnum]->numlumps; lump++, lump_p++)
if (memcmp(lump_p->name,"LUA_",4)==0)
LUA_LoadLump(wadnum, lump);
}
#endif
{
lumpinfo_t *lump_p = wadfiles[wadnum]->lumpinfo;
for (lump = 0; lump < wadfiles[wadnum]->numlumps; lump++, lump_p++)
if (memcmp(lump_p->name,"SOC_",4)==0) // Check for generic SOC lump
{ // shameless copy+paste of code from LUA_LoadLump
size_t length = strlen(wadfiles[wadnum]->filename) + 1 + strlen(lump_p->name2); // length of file name, '|', and lump name
char *name = malloc(length + 1);
sprintf(name, "%s|%s", wadfiles[wadnum]->filename, lump_p->name2);
name[length] = '\0';
CONS_Printf(M_GetText("Loading SOC from %s\n"), name);
DEH_LoadDehackedLumpPwad(wadnum, lump);
free(name);
}
else if (memcmp(lump_p->name,"MAINCFG",8)==0) // Check for MAINCFG
{
CONS_Printf(M_GetText("Loading main config from %s\n"), wadfiles[wadnum]->filename);
DEH_LoadDehackedLumpPwad(wadnum, lump);
}
else if (memcmp(lump_p->name,"OBJCTCFG",8)==0) // Check for OBJCTCFG
{
CONS_Printf(M_GetText("Loading object config from %s\n"), wadfiles[wadnum]->filename);
DEH_LoadDehackedLumpPwad(wadnum, lump);
}
}
#ifdef SCANTHINGS
// Scan maps for emblems 'n shit
{
lumpinfo_t *lump_p = wadfiles[wadnum]->lumpinfo;
for (lump = 0; lump < wadfiles[wadnum]->numlumps; lump++, lump_p++)
{
const char *name = lump_p->name;
if (name[0] == 'M' && name[1] == 'A' && name[2] == 'P' && name[5]=='\0')
{
INT16 mapnum = (INT16)M_MapNumber(name[3], name[4]);
P_ScanThings(mapnum, wadnum, lump + ML_THINGS);
}
}
}
#endif
}
/** Compute MD5 message digest for bytes read from STREAM of this filname.
*
* The resulting message digest number will be written into the 16 bytes
* beginning at RESBLOCK.
*
* \param filename path of file
* \param resblock resulting MD5 checksum
* \return 0 if MD5 checksum was made, and is at resblock, 1 if error was found
*/
static inline INT32 W_MakeFileMD5(const char *filename, void *resblock)
{
#ifdef NOMD5
(void)filename;
memset(resblock, 0x00, 16);
#else
FILE *fhandle;
if ((fhandle = fopen(filename, "rb")) != NULL)
{
tic_t t = I_GetTime();
CONS_Debug(DBG_SETUP, "Making MD5 for %s\n",filename);
if (md5_stream(fhandle, resblock) == 1)
{
fclose(fhandle);
return 1;
}
CONS_Debug(DBG_SETUP, "MD5 calc for %s took %f seconds\n",
filename, (float)(I_GetTime() - t)/NEWTICRATE);
fclose(fhandle);
return 0;
}
#endif
return 1;
}
// Invalidates the cache of lump numbers. Call this whenever a wad is added.
static void W_InvalidateLumpnumCache(void)
{
memset(lumpnumcache, 0, sizeof (lumpnumcache));
}
/** Detect a file type.
* \todo Actually detect the wad/pkzip headers and whatnot, instead of just checking the extensions.
*/
static restype_t ResourceFileDetect (const char* filename)
{
if (!stricmp(&filename[strlen(filename) - 4], ".pk3"))
return RET_PK3;
if (!stricmp(&filename[strlen(filename) - 4], ".soc"))
return RET_SOC;
if (!stricmp(&filename[strlen(filename) - 4], ".lua"))
return RET_LUA;
return RET_WAD;
}
/** Create a 1-lump lumpinfo_t for standalone files.
*/
static lumpinfo_t* ResGetLumpsStandalone (FILE* handle, UINT16* numlumps, const char* lumpname)
{
lumpinfo_t* lumpinfo = Z_Calloc(sizeof (*lumpinfo), PU_STATIC, NULL);
lumpinfo = Z_Calloc(sizeof (*lumpinfo), PU_STATIC, NULL);
lumpinfo->position = 0;
fseek(handle, 0, SEEK_END);
lumpinfo->size = ftell(handle);
fseek(handle, 0, SEEK_SET);
strcpy(lumpinfo->name, lumpname);
// Allocate the lump's full name.
lumpinfo->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
strcpy(lumpinfo->name2, lumpname);
lumpinfo->name2[8] = '\0';
*numlumps = 1;
return lumpinfo;
}
/** Create a lumpinfo_t array for a WAD file.
*/
static lumpinfo_t* ResGetLumpsWad (FILE* handle, UINT16* nlmp, const char* filename)
{
UINT16 numlumps = *nlmp;
lumpinfo_t* lumpinfo;
size_t i;
INT32 compressed = 0;
wadinfo_t header;
lumpinfo_t *lump_p;
filelump_t *fileinfo;
void *fileinfov;
// read the header
if (fread(&header, 1, sizeof header, handle) < sizeof header)
{
CONS_Alert(CONS_ERROR, M_GetText("Can't read wad header because %s\n"), M_FileError(handle));
return NULL;
}
if (memcmp(header.identification, "ZWAD", 4) == 0)
compressed = 1;
else if (memcmp(header.identification, "IWAD", 4) != 0
&& memcmp(header.identification, "PWAD", 4) != 0
&& memcmp(header.identification, "SDLL", 4) != 0)
{
CONS_Alert(CONS_ERROR, M_GetText("Invalid WAD header\n"));
return NULL;
}
header.numlumps = LONG(header.numlumps);
header.infotableofs = LONG(header.infotableofs);
// read wad file directory
i = header.numlumps * sizeof (*fileinfo);
fileinfov = fileinfo = malloc(i);
if (fseek(handle, header.infotableofs, SEEK_SET) == -1
|| fread(fileinfo, 1, i, handle) < i)
{
CONS_Alert(CONS_ERROR, M_GetText("Corrupt wadfile directory (%s)\n"), M_FileError(handle));
free(fileinfov);
return NULL;
}
numlumps = header.numlumps;
// fill in lumpinfo for this wad
lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL);
for (i = 0; i < numlumps; i++, lump_p++, fileinfo++)
{
lump_p->position = LONG(fileinfo->filepos);
lump_p->size = lump_p->disksize = LONG(fileinfo->size);
if (compressed) // wad is compressed, lump might be
{
UINT32 realsize = 0;
if (fseek(handle, lump_p->position, SEEK_SET)
== -1 || fread(&realsize, 1, sizeof realsize,
handle) < sizeof realsize)
{
I_Error("corrupt compressed file: %s; maybe %s", /// \todo Avoid the bailout?
filename, M_FileError(handle));
}
realsize = LONG(realsize);
if (realsize != 0)
{
lump_p->size = realsize;
lump_p->compression = CM_LZF;
}
else
{
lump_p->size -= 4;
lump_p->compression = CM_NOCOMPRESSION;
}
lump_p->position += 4;
lump_p->disksize -= 4;
}
else
lump_p->compression = CM_NOCOMPRESSION;
memset(lump_p->name, 0x00, 9);
strncpy(lump_p->name, fileinfo->name, 8);
// Allocate the lump's full name.
lump_p->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
strncpy(lump_p->name2, fileinfo->name, 8);
lump_p->name2[8] = '\0';
}
free(fileinfov);
*nlmp = numlumps;
return lumpinfo;
}
/** Optimized pattern search in a file.
*/
static boolean ResFindSignature (FILE* handle, char endPat[], UINT32 startpos)
{
char *s;
int c;
fseek(handle, startpos, SEEK_SET);
s = endPat;
while((c = fgetc(handle)) != EOF)
{
if (*s != c && s > endPat) // No match?
s = endPat; // We "reset" the counter by sending the s pointer back to the start of the array.
if (*s == c)
{
s++;
if (*s == 0x00) // The array pointer has reached the key char which marks the end. It means we have matched the signature.
{
return true;
}
}
}
return false;
}
#if defined(_MSC_VER)
#pragma pack(1)
#endif
typedef struct zend_s
{
char signature[4];
UINT16 diskpos;
UINT16 cdirdisk;
UINT16 diskentries;
UINT16 entries;
UINT32 cdirsize;
UINT32 cdiroffset;
UINT16 commentlen;
} ATTRPACK zend_t;
typedef struct zentry_s
{
char signature[4];
UINT16 version;
UINT16 versionneeded;
UINT16 flags;
UINT16 compression;
UINT16 modtime;
UINT16 moddate;
UINT32 CRC32;
UINT32 compsize;
UINT32 size;
UINT16 namelen;
UINT16 xtralen;
UINT16 commlen;
UINT16 diskstart;
UINT16 attrint;
UINT32 attrext;
UINT32 offset;
} ATTRPACK zentry_t;
typedef struct zlentry_s
{
char signature[4];
UINT16 versionneeded;
UINT16 flags;
UINT16 compression;
UINT16 modtime;
UINT16 moddate;
UINT32 CRC32;
UINT32 compsize;
UINT32 size;
UINT16 namelen;
UINT16 xtralen;
} ATTRPACK zlentry_t;
#if defined(_MSC_VER)
#pragma pack()
#endif
/** Create a lumpinfo_t array for a PKZip file.
*/
static lumpinfo_t* ResGetLumpsZip (FILE* handle, UINT16* nlmp)
{
zend_t zend;
zentry_t* zentries;
zentry_t* zentry;
UINT16 numlumps = *nlmp;
lumpinfo_t* lumpinfo;
lumpinfo_t *lump_p;
size_t i;
char pat_central[] = {0x50, 0x4b, 0x01, 0x02, 0x00};
char pat_end[] = {0x50, 0x4b, 0x05, 0x06, 0x00};
// Look for central directory end signature near end of file.
// Contains entry number (number of lumps), and central directory start offset.
fseek(handle, 0, SEEK_END);
if (!ResFindSignature(handle, pat_end, max(0, ftell(handle) - (22 + 65536))))
{
CONS_Alert(CONS_ERROR, "Missing central directory\n");
return NULL;
}
fseek(handle, -4, SEEK_CUR);
if (fread(&zend, 1, sizeof zend, handle) < sizeof zend)
{
CONS_Alert(CONS_ERROR, "Corrupt central directory (%s)\n", M_FileError(handle));
return NULL;
}
numlumps = zend.entries;
lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL);
zentry = zentries = malloc(numlumps * sizeof (*zentries));
fseek(handle, zend.cdiroffset, SEEK_SET);
for (i = 0; i < numlumps; i++, zentry++, lump_p++)
{
char* fullname;
char* trimname;
char* dotpos;
if (fread(zentry, 1, sizeof(zentry_t), handle) < sizeof(zentry_t))
{
CONS_Alert(CONS_ERROR, "Failed to read central directory (%s)\n", M_FileError(handle));
Z_Free(lumpinfo);
free(zentry);
return NULL;
}
if (memcmp(zentry->signature, pat_central, 4))
{
CONS_Alert(CONS_ERROR, "Central directory is corrupt\n");
Z_Free(lumpinfo);
free(zentry);
return NULL;
}
lump_p->position = zentry->offset + zentry->namelen + zentry->xtralen + sizeof(zlentry_t);
lump_p->disksize = zentry->compsize;
lump_p->size = zentry->size;
fullname = malloc(zentry->namelen + 1);
if (fgets(fullname, zentry->namelen + 1, handle) != fullname)
{
CONS_Alert(CONS_ERROR, "Unable to read lumpname (%s)\n", M_FileError(handle));
Z_Free(lumpinfo);
free(zentry);
free(fullname);
return NULL;
}
// Strip away file address and extension for the 8char name.
if ((trimname = strrchr(fullname, '/')) != 0)
trimname++;
else
trimname = fullname; // Care taken for root files.
if ((dotpos = strrchr(trimname, '.')) == 0)
dotpos = fullname + strlen(fullname); // Watch for files without extension.
memset(lump_p->name, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
strncpy(lump_p->name, trimname, min(8, dotpos - trimname));
lump_p->name2 = Z_Calloc(zentry->namelen + 1, PU_STATIC, NULL);
strncpy(lump_p->name2, fullname, zentry->namelen);
free(fullname);
switch(zentry->compression)
{
case 0:
lump_p->compression = CM_NOCOMPRESSION;
break;
#ifdef HAVE_ZLIB
case 8:
lump_p->compression = CM_DEFLATE;
break;
#endif
case 14:
lump_p->compression = CM_LZF;
break;
default:
CONS_Alert(CONS_WARNING, "%s: Unsupported compression method\n", fullname);
lump_p->compression = CM_UNSUPPORTED;
break;
}
}
*nlmp = numlumps;
return lumpinfo;
}
// Allocate a wadfile, setup the lumpinfo (directory) and
// lumpcache, add the wadfile to the current active wadfiles
//
// now returns index into wadfiles[], you can get wadfile_t *
// with:
// wadfiles[<return value>]
//
// return -1 in case of problem
//
// Can now load dehacked files (.soc)
//
UINT16 W_InitFile(const char *filename)
{
FILE *handle;
lumpinfo_t *lumpinfo = NULL;
wadfile_t *wadfile;
restype_t type;
UINT16 numlumps = 0;
size_t i;
UINT8 md5sum[16];
boolean important;
if (!(refreshdirmenu & REFRESHDIR_ADDFILE))
refreshdirmenu = REFRESHDIR_NORMAL|REFRESHDIR_ADDFILE; // clean out cons_alerts that happened earlier
if (refreshdirname)
Z_Free(refreshdirname);
if (dirmenu)
{
refreshdirname = Z_StrDup(filename);
nameonly(refreshdirname);
}
else
refreshdirname = NULL;
//CONS_Debug(DBG_SETUP, "Loading %s\n", filename);
//
// check if limit of active wadfiles
//
if (numwadfiles >= MAX_WADFILES)
{
CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n"));
refreshdirmenu |= REFRESHDIR_MAX;
return INT16_MAX;
}
// open wad file
if ((handle = W_OpenWadFile(&filename, true)) == NULL)
return INT16_MAX;
important = !W_VerifyNMUSlumps(filename);
#ifndef NOMD5
//
// w-waiiiit!
// Let's not add a wad file if the MD5 matches
// an MD5 of an already added WAD file!
//
W_MakeFileMD5(filename, md5sum);
for (i = 0; i < numwadfiles; i++)
{
if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
{
CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), filename);
if (handle)
fclose(handle);
return INT16_MAX;
}
}
#endif
switch(type = ResourceFileDetect(filename))
{
case RET_SOC:
lumpinfo = ResGetLumpsStandalone(handle, &numlumps, "OBJCTCFG");
break;
#ifdef HAVE_BLUA
case RET_LUA:
lumpinfo = ResGetLumpsStandalone(handle, &numlumps, "LUA_INIT");
break;
#endif
case RET_PK3:
lumpinfo = ResGetLumpsZip(handle, &numlumps);
break;
case RET_WAD:
lumpinfo = ResGetLumpsWad(handle, &numlumps, filename);
break;
default:
CONS_Alert(CONS_ERROR, "Unsupported file format\n");
}
if (lumpinfo == NULL)
{
fclose(handle);
return INT16_MAX;
}
//
// link wad file to search files
//
wadfile = Z_Malloc(sizeof (*wadfile), PU_STATIC, NULL);
wadfile->filename = Z_StrDup(filename);
wadfile->handle = handle;
wadfile->numlumps = (UINT16)numlumps;
wadfile->lumpinfo = lumpinfo;
wadfile->important = important;
fseek(handle, 0, SEEK_END);
wadfile->filesize = (unsigned)ftell(handle);
wadfile->type = type;
// already generated, just copy it over
M_Memcpy(&wadfile->md5sum, &md5sum, 16);
//
// set up caching
//
Z_Calloc(numlumps * sizeof (*wadfile->lumpcache), PU_STATIC, &wadfile->lumpcache);
#ifdef HWRENDER
// allocates GLPatch info structures and store them in a tree
wadfile->hwrcache = M_AATreeAlloc(AATREE_ZUSER);
#endif
//
// add the wadfile
//
CONS_Printf(M_GetText("Added file %s (%u lumps)\n"), filename, numlumps);
wadfiles[numwadfiles] = wadfile;
numwadfiles++; // must come BEFORE W_LoadDehackedLumps, so any addfile called by COM_BufInsertText called by Lua doesn't overwrite what we just loaded
#ifdef HWRENDER
if (rendermode == render_opengl)
HWR_LoadShaders(numwadfiles - 1, (wadfile->type == RET_PK3));
#endif
// TODO: HACK ALERT - Load Lua & SOC stuff right here. I feel like this should be out of this place, but... Let's stick with this for now.
switch (wadfile->type)
{
case RET_WAD:
W_LoadDehackedLumps(numwadfiles - 1);
break;
case RET_PK3:
W_LoadDehackedLumpsPK3(numwadfiles - 1);
break;
case RET_SOC:
CONS_Printf(M_GetText("Loading SOC from %s\n"), wadfile->filename);
DEH_LoadDehackedLumpPwad(numwadfiles - 1, 0);
break;
#ifdef HAVE_BLUA
case RET_LUA:
LUA_LoadLump(numwadfiles - 1, 0);
break;
#endif
default:
break;
}
if (refreshdirmenu & REFRESHDIR_GAMEDATA)
G_LoadGameData();
DEH_UpdateMaxFreeslots();
W_InvalidateLumpnumCache();
return wadfile->numlumps;
}
#ifdef DELFILE
void W_UnloadWadFile(UINT16 num)
{
INT32 i;
wadfile_t *delwad = wadfiles[num];
lumpcache_t *lumpcache;
if (num == 0)
return;
CONS_Printf(M_GetText("Removing WAD %s...\n"), wadfiles[num]->filename);
DEH_UnloadDehackedWad(num);
wadfiles[num] = NULL;
lumpcache = delwad->lumpcache;
numwadfiles--;
#ifdef HWRENDER
if (rendermode != render_soft && rendermode != render_none)
HWR_FreeTextureCache();
M_AATreeFree(delwad->hwrcache);
#endif
if (*lumpcache)
{
for (i = 0;i < delwad->numlumps;i++)
Z_ChangeTag(lumpcache[i], PU_PURGELEVEL);
}
Z_Free(lumpcache);
fclose(delwad->handle);
Z_Free(delwad->filename);
Z_Free(delwad);
CONS_Printf(M_GetText("Done unloading WAD.\n"));
}
#endif
/** Tries to load a series of files.
* All files are wads unless they have an extension of ".soc" or ".lua".
*
* Each file is optional, but at least one file must be found or an error will
* result. Lump names can appear multiple times. The name searcher looks
* backwards, so a later file overrides all earlier ones.
*
* \param filenames A null-terminated list of files to use.
* \return 1 if all files were loaded, 0 if at least one was missing or
* invalid.
*/
INT32 W_InitMultipleFiles(char **filenames, boolean addons)
{
INT32 rc = 1;
// will be realloced as lumps are added
for (; *filenames; filenames++)
{
if (addons && !W_VerifyNMUSlumps(*filenames))
G_SetGameModified(true, false);
//CONS_Debug(DBG_SETUP, "Loading %s\n", *filenames);
rc &= (W_InitFile(*filenames) != INT16_MAX) ? 1 : 0;
}
if (!numwadfiles)
I_Error("W_InitMultipleFiles: no files found");
return rc;
}
/** Make sure a lump number is valid.
* Compiles away to nothing if PARANOIA is not defined.
*/
static boolean TestValidLump(UINT16 wad, UINT16 lump)
{
I_Assert(wad < MAX_WADFILES);
if (!wadfiles[wad]) // make sure the wad file exists
return false;
I_Assert(lump < wadfiles[wad]->numlumps);
if (lump >= wadfiles[wad]->numlumps) // make sure the lump exists
return false;
return true;
}
const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump)
{
if (lump >= wadfiles[wad]->numlumps || !TestValidLump(wad, 0))
return NULL;
return wadfiles[wad]->lumpinfo[lump].name;
}
const char *W_CheckNameForNum(lumpnum_t lumpnum)
{
return W_CheckNameForNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
}
//
// Same as the original, but checks in one pwad only.
// wadid is a wad number
// (Used for sprites loading)
//
// 'startlump' is the lump number to start the search
//
UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump)
{
UINT16 i;
static char uname[9];
memset(uname, 0x00, sizeof uname);
strncpy(uname, name, 8);
uname[8] = 0;
strupr(uname);
if (!TestValidLump(wad,0))
return INT16_MAX;
//
// scan forward
// start at 'startlump', useful parameter when there are multiple
// resources with the same name
//
if (startlump < wadfiles[wad]->numlumps)
{
lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
{
if (memcmp(lump_p->name,uname,8) == 0)
return i;
}
}
// not found.
return INT16_MAX;
}
// Look for the first lump from a folder.
UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlump)
{
INT32 i;
lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
{
if (strnicmp(name, lump_p->name2, strlen(name)) == 0)
break;
}
return i;
}
// In a PK3 type of resource file, it looks for the next lumpinfo entry that doesn't share the specified pathfile.
// Useful for finding folder ends.
// Returns the position of the lumpinfo entry.
UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump)
{
INT32 i;
lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
{
if (strnicmp(name, lump_p->name2, strlen(name)))
break;
}
return i;
}
// In a PK3 type of resource file, it looks for an entry with the specified full name.
// Returns lump position in PK3's lumpinfo, or INT16_MAX if not found.
UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump)
{
INT32 i;
lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
{
if (!strnicmp(name, lump_p->name2, strlen(name)))
{
return i;
}
}
// Not found at all?
return INT16_MAX;
}
//
// W_CheckNumForName
// Returns LUMPERROR if name not found.
//
lumpnum_t W_CheckNumForName(const char *name)
{
INT32 i;
lumpnum_t check = INT16_MAX;
// Check the lumpnumcache first. Loop backwards so that we check
// most recent entries first
for (i = lumpnumcacheindex + LUMPNUMCACHESIZE; i > lumpnumcacheindex; i--)
{
if (strncmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name, 8) == 0)
{
lumpnumcacheindex = i & (LUMPNUMCACHESIZE - 1);
return lumpnumcache[lumpnumcacheindex].lumpnum;
}
}
// scan wad files backwards so patch lump files take precedence
for (i = numwadfiles - 1; i >= 0; i--)
{
check = W_CheckNumForNamePwad(name,(UINT16)i,0);
if (check != INT16_MAX)
break; //found it
}
if (check == INT16_MAX) return LUMPERROR;
else
{
// Update the cache.
lumpnumcacheindex = (lumpnumcacheindex + 1) & (LUMPNUMCACHESIZE - 1);
strncpy(lumpnumcache[lumpnumcacheindex].lumpname, name, 8);
lumpnumcache[lumpnumcacheindex].lumpnum = (i<<16)+check;
return lumpnumcache[lumpnumcacheindex].lumpnum;
}
}
// Look for valid map data through all added files in descendant order.
// Get a map marker for WADs, and a standalone WAD file lump inside PK3s.
// TODO: Make it search through cache first, maybe...?
lumpnum_t W_CheckNumForMap(const char *name)
{
UINT16 lumpNum, end;
UINT32 i;
for (i = numwadfiles - 1; i < numwadfiles; i--)
{
if (wadfiles[i]->type == RET_WAD)
{
for (lumpNum = 0; lumpNum < wadfiles[i]->numlumps; lumpNum++)
if (!strncmp(name, (wadfiles[i]->lumpinfo + lumpNum)->name, 8))
return (i<<16) + lumpNum;
}
else if (wadfiles[i]->type == RET_PK3)
{
lumpNum = W_CheckNumForFolderStartPK3("maps/", i, 0);
if (lumpNum != INT16_MAX)
end = W_CheckNumForFolderEndPK3("maps/", i, lumpNum);
else
continue;
// Now look for the specified map.
for (++lumpNum; lumpNum < end; lumpNum++)
if (!strnicmp(name, (wadfiles[i]->lumpinfo + lumpNum)->name, 8))
return (i<<16) + lumpNum;
}
}
return LUMPERROR;
}
//
// W_GetNumForName
//
// Calls W_CheckNumForName, but bombs out if not found.
//
lumpnum_t W_GetNumForName(const char *name)
{
lumpnum_t i;
i = W_CheckNumForName(name);
if (i == LUMPERROR)
I_Error("W_GetNumForName: %s not found!\n", name);
return i;
}
//
// W_CheckNumForNameInBlock
// Checks only in blocks from blockstart lump to blockend lump
//
lumpnum_t W_CheckNumForNameInBlock(const char *name, const char *blockstart, const char *blockend)
{
INT32 i;
lumpnum_t bsid, beid;
lumpnum_t check = INT16_MAX;
// scan wad files backwards so patch lump files take precedence
for (i = numwadfiles - 1; i >= 0; i--)
{
if (wadfiles[i]->type == RET_WAD)
{
bsid = W_CheckNumForNamePwad(blockstart, (UINT16)i, 0);
if (bsid == INT16_MAX)
continue; // Start block doesn't exist?
beid = W_CheckNumForNamePwad(blockend, (UINT16)i, 0);
if (beid == INT16_MAX)
continue; // End block doesn't exist?
check = W_CheckNumForNamePwad(name, (UINT16)i, bsid);
if (check < beid)
return (i<<16)+check; // found it, in our constraints
}
}
return LUMPERROR;
}
// Used by Lua. Case sensitive lump checking, quickly...
#include "fastcmp.h"
UINT8 W_LumpExists(const char *name)
{
INT32 i,j;
for (i = numwadfiles - 1; i >= 0; i--)
{
lumpinfo_t *lump_p = wadfiles[i]->lumpinfo;
for (j = 0; j < wadfiles[i]->numlumps; ++j, ++lump_p)
if (fastcmp(lump_p->name,name))
return true;
}
return false;
}
size_t W_LumpLengthPwad(UINT16 wad, UINT16 lump)
{
if (!TestValidLump(wad, lump))
return 0;
return wadfiles[wad]->lumpinfo[lump].size;
}
/** Returns the buffer size needed to load the given lump.
*
* \param lump Lump number to look at.
* \return Buffer size needed, in bytes.
*/
size_t W_LumpLength(lumpnum_t lumpnum)
{
return W_LumpLengthPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
}
//
// W_IsLumpWad
// Is the lump a WAD? (presumably in a PK3)
//
boolean W_IsLumpWad(lumpnum_t lumpnum)
{
if (wadfiles[WADFILENUM(lumpnum)]->type == RET_PK3)
{
const char *lumpfullName = (wadfiles[WADFILENUM(lumpnum)]->lumpinfo + LUMPNUM(lumpnum))->name2;
if (strlen(lumpfullName) < 4)
return false; // can't possibly be a WAD can it?
return !strnicmp(lumpfullName + strlen(lumpfullName) - 4, ".wad", 4);
}
return false; // WADs should never be inside non-PK3s as far as SRB2 is concerned
}
#ifdef HAVE_ZLIB
/* report a zlib or i/o error */
void zerr(int ret)
{
CONS_Printf("zpipe: ");
switch (ret) {
case Z_ERRNO:
if (ferror(stdin))
CONS_Printf("error reading stdin\n");
if (ferror(stdout))
CONS_Printf("error writing stdout\n");
break;
case Z_STREAM_ERROR:
CONS_Printf("invalid compression level\n");
break;
case Z_DATA_ERROR:
CONS_Printf("invalid or incomplete deflate data\n");
break;
case Z_MEM_ERROR:
CONS_Printf("out of memory\n");
break;
case Z_VERSION_ERROR:
CONS_Printf("zlib version mismatch!\n");
}
}
#endif
#define NO_PNG_LUMPS
#ifdef NO_PNG_LUMPS
static void ErrorIfPNG(UINT8 *d, size_t s, char *f, char *l)
{
if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/
return;
// Check for PNG file signature using memcmp
// As it may be faster on CPUs with slow unaligned memory access
// Ref: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature
if (memcmp(&d[0], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0)
{
I_Error("W_Wad: Lump \"%s\" in file \"%s\" is a .PNG - please convert to either Doom or Flat (raw) image format.", l, f);
}
}
#endif
/** Reads bytes from the head of a lump.
* Note: If the lump is compressed, the whole thing has to be read anyway.
*
* \param wad Wad number to read from.
* \param lump Lump number to read from.
* \param dest Buffer in memory to serve as destination.
* \param size Number of bytes to read.
* \param offest Number of bytes to offset.
* \return Number of bytes read (should equal size).
* \sa W_ReadLump, W_RawReadLumpHeader
*/
size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, size_t offset)
{
size_t lumpsize;
lumpinfo_t *l;
FILE *handle;
if (!TestValidLump(wad,lump))
return 0;
lumpsize = wadfiles[wad]->lumpinfo[lump].size;
// empty resource (usually markers like S_START, F_END ..)
if (!lumpsize || lumpsize<offset)
return 0;
// zero size means read all the lump
if (!size || size+offset > lumpsize)
size = lumpsize - offset;
// Let's get the raw lump data.
// We setup the desired file handle to read the lump data.
l = wadfiles[wad]->lumpinfo + lump;
handle = wadfiles[wad]->handle;
fseek(handle, (long)(l->position + offset), SEEK_SET);
// But let's not copy it yet. We support different compression formats on lumps, so we need to take that into account.
switch(wadfiles[wad]->lumpinfo[lump].compression)
{
case CM_NOCOMPRESSION: // If it's uncompressed, we directly write the data into our destination, and return the bytes read.
#ifdef NO_PNG_LUMPS
{
size_t bytesread = fread(dest, 1, size, handle);
ErrorIfPNG(dest, bytesread, wadfiles[wad]->filename, l->name2);
return bytesread;
}
#else
return fread(dest, 1, size, handle);
#endif
case CM_LZF: // Is it LZF compressed? Used by ZWADs.
{
#ifdef ZWAD
char *rawData; // The lump's raw data.
char *decData; // Lump's decompressed real data.
size_t retval; // Helper var, lzf_decompress returns 0 when an error occurs.
rawData = Z_Malloc(l->disksize, PU_STATIC, NULL);
decData = Z_Malloc(l->size, PU_STATIC, NULL);
if (fread(rawData, 1, l->disksize, handle) < l->disksize)
I_Error("wad %d, lump %d: cannot read compressed data", wad, lump);
retval = lzf_decompress(rawData, l->disksize, decData, l->size);
#ifndef AVOID_ERRNO
if (retval == 0) // If this was returned, check if errno was set
{
// errno is a global var set by the lzf functions when something goes wrong.
if (errno == E2BIG)
I_Error("wad %d, lump %d: compressed data too big (bigger than %s)", wad, lump, sizeu1(l->size));
else if (errno == EINVAL)
I_Error("wad %d, lump %d: invalid compressed data", wad, lump);
}
// Otherwise, fall back on below error (if zero was actually the correct size then ???)
#endif
if (retval != l->size)
{
I_Error("wad %d, lump %d: decompressed to wrong number of bytes (expected %s, got %s)", wad, lump, sizeu1(l->size), sizeu2(retval));
}
if (!decData) // Did we get no data at all?
return 0;
M_Memcpy(dest, decData + offset, size);
Z_Free(rawData);
Z_Free(decData);
#ifdef NO_PNG_LUMPS
ErrorIfPNG(dest, size, wadfiles[wad]->filename, l->name2);
#endif
return size;
#else
//I_Error("ZWAD files not supported on this platform.");
return 0;
#endif
}
#ifdef HAVE_ZLIB
case CM_DEFLATE: // Is it compressed via DEFLATE? Very common in ZIPs/PK3s, also what most doom-related editors support.
{
UINT8 *rawData; // The lump's raw data.
UINT8 *decData; // Lump's decompressed real data.
int zErr; // Helper var.
z_stream strm;
unsigned long rawSize = l->disksize;
unsigned long decSize = l->size;
rawData = Z_Malloc(rawSize, PU_STATIC, NULL);
decData = Z_Malloc(decSize, PU_STATIC, NULL);
if (fread(rawData, 1, rawSize, handle) < rawSize)
I_Error("wad %d, lump %d: cannot read compressed data", wad, lump);
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_in = strm.avail_in = rawSize;
strm.total_out = strm.avail_out = decSize;
strm.next_in = rawData;
strm.next_out = decData;
zErr = inflateInit2(&strm, -15);
if (zErr == Z_OK)
{
zErr = inflate(&strm, Z_FINISH);
if (zErr == Z_STREAM_END)
{
M_Memcpy(dest, decData, size);
}
else
{
size = 0;
zerr(zErr);
}
(void)inflateEnd(&strm);
}
else
{
CONS_Printf("whopet\n");
size = 0;
zerr(zErr);
}
Z_Free(rawData);
Z_Free(decData);
#ifdef NO_PNG_LUMPS
ErrorIfPNG(dest, size, wadfiles[wad]->filename, l->name2);
#endif
return size;
}
#endif
default:
I_Error("wad %d, lump %d: unsupported compression type!", wad, lump);
}
return -1;
}
size_t W_ReadLumpHeader(lumpnum_t lumpnum, void *dest, size_t size, size_t offset)
{
return W_ReadLumpHeaderPwad(WADFILENUM(lumpnum), LUMPNUM(lumpnum), dest, size, offset);
}
/** Reads a lump into memory.
*
* \param lump Lump number to read from.
* \param dest Buffer in memory to serve as destination. Size must be >=
* W_LumpLength().
* \sa W_ReadLumpHeader
*/
void W_ReadLump(lumpnum_t lumpnum, void *dest)
{
W_ReadLumpHeaderPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum),dest,0,0);
}
void W_ReadLumpPwad(UINT16 wad, UINT16 lump, void *dest)
{
W_ReadLumpHeaderPwad(wad, lump, dest, 0, 0);
}
// ==========================================================================
// W_CacheLumpNum
// ==========================================================================
void *W_CacheLumpNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
{
lumpcache_t *lumpcache;
if (!TestValidLump(wad,lump))
return NULL;
lumpcache = wadfiles[wad]->lumpcache;
if (!lumpcache[lump])
{
void *ptr = Z_Malloc(W_LumpLengthPwad(wad, lump), tag, &lumpcache[lump]);
W_ReadLumpHeaderPwad(wad, lump, ptr, 0, 0); // read the lump in full
}
else
Z_ChangeTag(lumpcache[lump], tag);
return lumpcache[lump];
}
void *W_CacheLumpNum(lumpnum_t lumpnum, INT32 tag)
{
return W_CacheLumpNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum),tag);
}
//
// W_CacheLumpNumForce
//
// Forces the lump to be loaded, even if it already is!
//
void *W_CacheLumpNumForce(lumpnum_t lumpnum, INT32 tag)
{
UINT16 wad, lump;
void *ptr;
wad = WADFILENUM(lumpnum);
lump = LUMPNUM(lumpnum);
if (!TestValidLump(wad,lump))
return NULL;
ptr = Z_Malloc(W_LumpLengthPwad(wad, lump), tag, NULL);
W_ReadLumpHeaderPwad(wad, lump, ptr, 0, 0); // read the lump in full
return ptr;
}
//
// W_IsLumpCached
//
// If a lump is already cached return true, otherwise
// return false.
//
// no outside code uses the PWAD form, for now
static inline boolean W_IsLumpCachedPWAD(UINT16 wad, UINT16 lump, void *ptr)
{
void *lcache;
if (!TestValidLump(wad, lump))
return false;
lcache = wadfiles[wad]->lumpcache[lump];
if (ptr)
{
if (ptr == lcache)
return true;
}
else if (lcache)
return true;
return false;
}
boolean W_IsLumpCached(lumpnum_t lumpnum, void *ptr)
{
return W_IsLumpCachedPWAD(WADFILENUM(lumpnum),LUMPNUM(lumpnum), ptr);
}
// ==========================================================================
// W_CacheLumpName
// ==========================================================================
void *W_CacheLumpName(const char *name, INT32 tag)
{
return W_CacheLumpNum(W_GetNumForName(name), tag);
}
// ==========================================================================
// CACHING OF GRAPHIC PATCH RESOURCES
// ==========================================================================
// Graphic 'patches' are loaded, and if necessary, converted into the format
// the most useful for the current rendermode. For software renderer, the
// graphic patches are kept as is. For the hardware renderer, graphic patches
// are 'unpacked', and are kept into the cache in that unpacked format, and
// the heap memory cache then acts as a 'level 2' cache just after the
// graphics card memory.
//
// Cache a patch into heap memory, convert the patch format as necessary
//
// Software-only compile cache the data without conversion
#ifdef HWRENDER
static inline void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
{
GLPatch_t *grPatch;
if (rendermode == render_soft || rendermode == render_none)
return W_CacheLumpNumPwad(wad, lump, tag);
if (!TestValidLump(wad, lump))
return NULL;
grPatch = HWR_GetCachedGLPatchPwad(wad, lump);
if (grPatch->mipmap->grInfo.data)
{
if (tag == PU_CACHE)
tag = PU_HWRCACHE;
Z_ChangeTag(grPatch->mipmap->grInfo.data, tag);
}
else
{
patch_t *ptr = NULL;
// Only load the patch if we haven't initialised the grPatch yet
if (grPatch->mipmap->width == 0)
ptr = W_CacheLumpNumPwad(grPatch->wadnum, grPatch->lumpnum, PU_STATIC);
// Run HWR_MakePatch in all cases, to recalculate some things
HWR_MakePatch(ptr, grPatch, grPatch->mipmap, false);
Z_Free(ptr);
}
// return GLPatch_t, which can be casted to (patch_t) with valid patch header info
return (void *)grPatch;
}
void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag)
{
return W_CachePatchNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum),tag);
}
#endif // HWRENDER
void W_UnlockCachedPatch(void *patch)
{
// The hardware code does its own memory management, as its patches
// have different lifetimes from software's.
#ifdef HWRENDER
if (rendermode != render_soft && rendermode != render_none)
HWR_UnlockCachedPatch((GLPatch_t*)patch);
else
#endif
Z_Unlock(patch);
}
void *W_CachePatchName(const char *name, INT32 tag)
{
lumpnum_t num;
num = W_CheckNumForName(name);
if (num == LUMPERROR)
return W_CachePatchNum(W_GetNumForName("MISSING"), tag);
return W_CachePatchNum(num, tag);
}
#ifndef NOMD5
/**
* Prints an MD5 string into a human-readable textual format.
*
* \param md5 The md5 in binary form -- MD5_LEN (16) bytes.
* \param buf Where to print the textual form. Needs 2*MD5_LEN+1 (33) bytes.
* \author Graue <graue@oceanbase.org>
*/
#define MD5_FORMAT \
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
static void PrintMD5String(const UINT8 *md5, char *buf)
{
snprintf(buf, 2*MD5_LEN+1, MD5_FORMAT,
md5[0], md5[1], md5[2], md5[3],
md5[4], md5[5], md5[6], md5[7],
md5[8], md5[9], md5[10], md5[11],
md5[12], md5[13], md5[14], md5[15]);
}
#endif
/** Verifies a file's MD5 is as it should be.
* For releases, used as cheat prevention -- if the MD5 doesn't match, a
* fatal error is thrown. In debug mode, an MD5 mismatch only triggers a
* warning.
*
* \param wadfilenum Number of the loaded wad file to check.
* \param matchmd5 The MD5 sum this wad should have, expressed as a
* textual string.
* \author Graue <graue@oceanbase.org>
*/
void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5)
{
#ifdef NOMD5
(void)wadfilenum;
(void)matchmd5;
#else
UINT8 realmd5[MD5_LEN];
INT32 ix;
I_Assert(strlen(matchmd5) == 2*MD5_LEN);
I_Assert(wadfilenum < numwadfiles);
// Convert an md5 string like "7d355827fa8f981482246d6c95f9bd48"
// into a real md5.
for (ix = 0; ix < 2*MD5_LEN; ix++)
{
INT32 n, c = matchmd5[ix];
if (isdigit(c))
n = c - '0';
else
{
I_Assert(isxdigit(c));
if (isupper(c)) n = c - 'A' + 10;
else n = c - 'a' + 10;
}
if (ix & 1) realmd5[ix>>1] = (UINT8)(realmd5[ix>>1]+n);
else realmd5[ix>>1] = (UINT8)(n<<4);
}
if (memcmp(realmd5, wadfiles[wadfilenum]->md5sum, 16))
{
char actualmd5text[2*MD5_LEN+1];
PrintMD5String(wadfiles[wadfilenum]->md5sum, actualmd5text);
#ifdef _DEBUG
CONS_Printf
#else
I_Error
#endif
(M_GetText("File is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);
}
#endif
}
// Note: This never opens lumps themselves and therefore doesn't have to
// deal with compressed lumps.
static int W_VerifyFile(const char *filename, lumpchecklist_t *checklist,
boolean status)
{
FILE *handle;
size_t i, j;
int goodfile = false;
if (!checklist)
I_Error("No checklist for %s\n", filename);
// open wad file
if ((handle = W_OpenWadFile(&filename, false)) == NULL)
return -1;
// detect wad file by the absence of the other supported extensions
if (stricmp(&filename[strlen(filename) - 4], ".soc")
#ifdef HAVE_BLUA
&& stricmp(&filename[strlen(filename) - 4], ".lua")
#endif
&& stricmp(&filename[strlen(filename) - 4], ".pk3"))
{
// assume wad file
wadinfo_t header;
filelump_t lumpinfo;
// read the header
if (fread(&header, 1, sizeof header, handle) == sizeof header
&& header.numlumps < INT16_MAX
&& strncmp(header.identification, "ZWAD", 4)
&& strncmp(header.identification, "IWAD", 4)
&& strncmp(header.identification, "PWAD", 4)
&& strncmp(header.identification, "SDLL", 4))
{
fclose(handle);
return true;
}
header.numlumps = LONG(header.numlumps);
header.infotableofs = LONG(header.infotableofs);
// let seek to the lumpinfo list
if (fseek(handle, header.infotableofs, SEEK_SET) == -1)
{
fclose(handle);
return false;
}
goodfile = true;
for (i = 0; i < header.numlumps; i++)
{
// fill in lumpinfo for this wad file directory
if (fread(&lumpinfo, sizeof (lumpinfo), 1 , handle) != 1)
{
fclose(handle);
return -1;
}
lumpinfo.filepos = LONG(lumpinfo.filepos);
lumpinfo.size = LONG(lumpinfo.size);
if (lumpinfo.size == 0)
continue;
for (j = 0; j < NUMSPRITES; j++)
if (sprnames[j] && !strncmp(lumpinfo.name, sprnames[j], 4)) // Sprites
continue;
goodfile = false;
for (j = 0; checklist[j].len && checklist[j].name && !goodfile; j++)
if ((strncmp(lumpinfo.name, checklist[j].name, checklist[j].len) != false) == status)
goodfile = true;
if (!goodfile)
break;
}
}
fclose(handle);
return goodfile;
}
/** Checks a wad for lumps other than music and sound.
* Used during game load to verify music.dta is a good file and during a
* netgame join (on the server side) to see if a wad is important enough to
* be sent.
*
* \param filename Filename of the wad to check.
* \return 1 if file contains only music/sound lumps, 0 if it contains other
* stuff (maps, sprites, dehacked lumps, and so on). -1 if there no
* file exists with that filename
* \author Alam Arias
*/
int W_VerifyNMUSlumps(const char *filename)
{
lumpchecklist_t NMUSlist[] =
{
{"D_", 2}, // MIDI music
{"O_", 2}, // Digital music
{"DS", 2}, // Sound effects
{"ENDOOM", 6}, // ENDOOM text lump
{"PLAYPAL", 7}, // Palette
{"COLORMAP", 8}, // Colormap
{"PAL", 3}, // Palette changes
{"CLM", 3}, // Colormap changes
{"TRANS", 5}, // Translucency map
{"LTFNT", 5}, // Level title font changes
{"TTL", 3}, // Act number changes
{"STCFN", 5}, // Console font changes
{"TNYFN", 5}, // Tiny console font changes
{"SBO", 3}, // Acceptable HUD changes (Score Time Rings)
{"RRINGS", 6}, // Rings HUD (not named as SBO)
{"YB_", 3}, // Intermission graphics, goes with the above
{"M_", 2}, // As does menu stuff
{"MKFNT", 5}, // Kart font changes
{"K_", 2}, // Kart graphic changes
{"MUSICDEF", 8}, // Kart song definitions
#ifdef HWRENDER
{"SHADERS", 7},
{"SH_", 3},
#endif
{NULL, 0},
};
return W_VerifyFile(filename, NMUSlist, false);
}