diff --git a/src/resourcefiles/file_wad.cpp b/src/resourcefiles/file_wad.cpp index a0f04269c..219f851eb 100644 --- a/src/resourcefiles/file_wad.cpp +++ b/src/resourcefiles/file_wad.cpp @@ -35,24 +35,267 @@ #include "resourcefile.h" #include "cmdlib.h" +#include "templates.h" #include "v_text.h" #include "w_wad.h" +// Console Doom LZSS wrapper. +class FileReaderLZSS : public FileReaderBase +{ +private: + enum { BUFF_SIZE = 4096, WINDOW_SIZE = 4096, INTERNAL_BUFFER_SIZE = 128 }; + + FileReader &File; + bool SawEOF; + BYTE InBuff[BUFF_SIZE]; + + enum StreamState + { + STREAM_EMPTY, + STREAM_BITS, + STREAM_FLUSH, + STREAM_FINAL + }; + struct + { + StreamState State; + + BYTE *In; + unsigned int AvailIn; + unsigned int InternalOut; + + BYTE CFlags, Bits; + + BYTE Window[WINDOW_SIZE+INTERNAL_BUFFER_SIZE]; + const BYTE *WindowData; + BYTE *InternalBuffer; + } Stream; + + void FillBuffer() + { + if(Stream.AvailIn) + memmove(InBuff, Stream.In, Stream.AvailIn); + + long numread = File.Read(InBuff+Stream.AvailIn, BUFF_SIZE-Stream.AvailIn); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + Stream.In = InBuff; + Stream.AvailIn = numread+Stream.AvailIn; + } + + // Reads a flag byte. + void PrepareBlocks() + { + assert(Stream.InternalBuffer == Stream.WindowData); + Stream.CFlags = *Stream.In++; + --Stream.AvailIn; + Stream.Bits = 0xFF; + Stream.State = STREAM_BITS; + } + + // Reads the next chunk in the block. Returns true if successful and + // returns false if it ran out of input data. + bool UncompressBlock() + { + if(Stream.CFlags & 1) + { + // Check to see if we have enough input + if(Stream.AvailIn < 2) + return false; + Stream.AvailIn -= 2; + + WORD pos = BigShort(*(WORD*)Stream.In); + BYTE len = (pos & 0xF)+1; + pos >>= 4; + Stream.In += 2; + if(len == 1) + { + // We've reached the end of the stream. + Stream.State = STREAM_FINAL; + return true; + } + + const BYTE* copyStart = Stream.InternalBuffer-pos-1; + + // Complete overlap: Single byte repeated + if(pos == 0) + memset(Stream.InternalBuffer, *copyStart, len); + // No overlap: One copy + else if(pos >= len) + memcpy(Stream.InternalBuffer, copyStart, len); + else + { + // Partial overlap: Copy in 2 or 3 chunks. + do + { + unsigned int copy = MIN(len, pos+1); + memcpy(Stream.InternalBuffer, copyStart, copy); + Stream.InternalBuffer += copy; + Stream.InternalOut += copy; + len -= copy; + pos += copy; // Increase our position since we can copy twice as much the next round. + } + while(len); + } + + Stream.InternalOut += len; + Stream.InternalBuffer += len; + } + else + { + // Uncompressed byte. + *Stream.InternalBuffer++ = *Stream.In++; + --Stream.AvailIn; + ++Stream.InternalOut; + } + + Stream.CFlags >>= 1; + Stream.Bits >>= 1; + + // If we're done with this block, flush the output + if(Stream.Bits == 0) + Stream.State = STREAM_FLUSH; + + return true; + } + +public: + FileReaderLZSS(FileReader &file) : File(file), SawEOF(false) + { + Stream.State = STREAM_EMPTY; + Stream.WindowData = Stream.InternalBuffer = Stream.Window+WINDOW_SIZE; + Stream.InternalOut = 0; + Stream.AvailIn = 0; + + FillBuffer(); + } + + ~FileReaderLZSS() + { + } + + long Read(void *buffer, long len) + { + + BYTE *Out = (BYTE*)buffer; + long AvailOut = len; + + do + { + while(Stream.AvailIn) + { + if(Stream.State == STREAM_EMPTY) + PrepareBlocks(); + else if(Stream.State == STREAM_BITS && !UncompressBlock()) + break; + else + break; + } + + unsigned int copy = MIN(Stream.InternalOut, AvailOut); + if(copy > 0) + { + memcpy(Out, Stream.WindowData, copy); + Out += copy; + AvailOut -= copy; + + // Slide our window + memmove(Stream.Window, Stream.Window+copy, WINDOW_SIZE+INTERNAL_BUFFER_SIZE-copy); + Stream.InternalBuffer -= copy; + Stream.InternalOut -= copy; + } + + if(Stream.State == STREAM_FINAL) + break; + + if(Stream.InternalOut == 0 && Stream.State == STREAM_FLUSH) + Stream.State = STREAM_EMPTY; + + if(Stream.AvailIn < 2) + FillBuffer(); + } + while(AvailOut && Stream.State != STREAM_FINAL); + + assert(AvailOut == 0); + return Out - (BYTE*)buffer; + } +}; + +//========================================================================== +// +// Wad Lump (with console doom LZSS support) +// +//========================================================================== + +class FWadFileLump : public FResourceLump +{ +public: + bool Compressed; + int Position; + + int GetFileOffset() { return Position; } + FileReader *GetReader() + { + if(!Compressed) + { + Owner->Reader->Seek(Position, SEEK_SET); + return Owner->Reader; + } + return NULL; + } + int FillCache() + { + if(!Compressed) + { + const char * buffer = Owner->Reader->GetBuffer(); + + if (buffer != NULL) + { + // This is an in-memory file so the cache can point directly to the file's data. + Cache = const_cast(buffer) + Position; + RefCount = -1; + return -1; + } + } + + Owner->Reader->Seek(Position, SEEK_SET); + Cache = new char[LumpSize]; + + if(Compressed) + { + FileReaderLZSS lzss(*Owner->Reader); + lzss.Read(Cache, LumpSize); + } + else + Owner->Reader->Read(Cache, LumpSize); + + RefCount = 1; + return 1; + } +}; + //========================================================================== // // Wad file // //========================================================================== -class FWadFile : public FUncompressedFile +class FWadFile : public FResourceFile { + FWadFileLump *Lumps; + bool IsMarker(int lump, const char *marker); void SetNamespace(const char *startmarker, const char *endmarker, namespace_t space, bool flathack=false); void SkinHack (); public: FWadFile(const char * filename, FileReader *file); + ~FWadFile(); void FindStrifeTeaserVoices (); + FResourceLump *GetLump(int lump) { return &Lumps[lump]; } bool Open(bool quiet); }; @@ -65,11 +308,16 @@ public: // //========================================================================== -FWadFile::FWadFile(const char *filename, FileReader *file) : FUncompressedFile(filename, file) +FWadFile::FWadFile(const char *filename, FileReader *file) : FResourceFile(filename, file) { Lumps = NULL; } +FWadFile::~FWadFile() +{ + delete[] Lumps; +} + //========================================================================== // // Open it @@ -79,33 +327,76 @@ FWadFile::FWadFile(const char *filename, FileReader *file) : FUncompressedFile(f bool FWadFile::Open(bool quiet) { wadinfo_t header; + DWORD InfoTableOfs; + bool isBigEndian = false; // Little endian is assumed until proven otherwise + const long wadSize = Reader->GetLength(); Reader->Read(&header, sizeof(header)); NumLumps = LittleLong(header.NumLumps); - header.InfoTableOfs = LittleLong(header.InfoTableOfs); - - wadlump_t *fileinfo = new wadlump_t[NumLumps]; - Reader->Seek (header.InfoTableOfs, SEEK_SET); - Reader->Read (fileinfo, NumLumps * sizeof(wadlump_t)); + InfoTableOfs = LittleLong(header.InfoTableOfs); - Lumps = new FUncompressedLump[NumLumps]; - - if (!quiet) Printf(", %d lumps\n", NumLumps); - - for(DWORD i = 0; i < NumLumps; i++) + // Check to see if the little endian interpretation is valid + // This should detect most big endian wads + if (InfoTableOfs + NumLumps*sizeof(wadlump_t) > (unsigned)wadSize) { - uppercopy (Lumps[i].Name, fileinfo[i].Name); - Lumps[i].Name[8] = 0; - Lumps[i].Owner = this; - Lumps[i].Position = LittleLong(fileinfo[i].FilePos); - Lumps[i].LumpSize = LittleLong(fileinfo[i].Size); - Lumps[i].Namespace = ns_global; - Lumps[i].Flags = 0; - Lumps[i].FullName = NULL; + NumLumps = BigLong(header.NumLumps); + InfoTableOfs = BigLong(header.InfoTableOfs); + isBigEndian = true; } - if (!quiet) // don't bother with namespaces here. We won't need them. + // Read the directory. If we're still assuming little endian and the lump + // is out of range then we switch to big endian mode and try again. + do { + wadlump_t *fileinfo = new wadlump_t[NumLumps]; + Reader->Seek (InfoTableOfs, SEEK_SET); + Reader->Read (fileinfo, NumLumps * sizeof(wadlump_t)); + + Lumps = new FWadFileLump[NumLumps]; + + bool valid = true; + for(DWORD i = 0; i < NumLumps; i++) + { + uppercopy (Lumps[i].Name, fileinfo[i].Name); + Lumps[i].Name[8] = 0; + Lumps[i].Compressed = Lumps[i].Name[0] & 0x80; + Lumps[i].Name[0] &= ~0x80; + + Lumps[i].Owner = this; + Lumps[i].Position = isBigEndian ? BigLong(fileinfo[i].FilePos) : LittleLong(fileinfo[i].FilePos); + Lumps[i].LumpSize = isBigEndian ? BigLong(fileinfo[i].Size) : LittleLong(fileinfo[i].Size); + if(Lumps[i].Position > wadSize || Lumps[i].Position + Lumps[i].LumpSize > wadSize) + { + valid = false; + break; + } + Lumps[i].Namespace = ns_global; + Lumps[i].Flags = 0; + Lumps[i].FullName = NULL; + } + + delete[] fileinfo; + if(!valid) + { + if(isBigEndian) + return false; + + delete[] Lumps; + NumLumps = BigLong(header.NumLumps); + InfoTableOfs = BigLong(header.InfoTableOfs); + isBigEndian = true; + continue; + } + + break; + } + while(true); + + if (!quiet) + { + Printf(", %d lumps\n", NumLumps); + + // don't bother with namespaces here. We won't need them. SetNamespace("S_START", "S_END", ns_sprites); SetNamespace("F_START", "F_END", ns_flats, true); SetNamespace("C_START", "C_END", ns_colormaps); @@ -116,7 +407,6 @@ bool FWadFile::Open(bool quiet) SetNamespace("VX_START", "VX_END", ns_voxels); SkinHack(); } - delete [] fileinfo; return true; }