From 32ede813e9a1ca557e75f7c67699419c7ecc10ae Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 19 Aug 2023 18:11:38 +0200 Subject: [PATCH] - new files for backend update. --- source/common/filesystem/files.cpp | 494 ++++++++++++ source/common/filesystem/files.h | 398 +++++++++ source/common/filesystem/files_decompress.cpp | 754 ++++++++++++++++++ source/common/filesystem/filesystem.h | 1 - source/common/filesystem/fs_findfile.cpp | 389 +++++++++ source/common/filesystem/fs_findfile.h | 35 + source/common/filesystem/fs_swap.h | 121 +++ source/common/textures/formats/qoitexture.cpp | 213 +++++ source/games/duke/src/sounds.cpp | 1 - source/games/sw/src/sounds.cpp | 12 - 10 files changed, 2404 insertions(+), 14 deletions(-) create mode 100644 source/common/filesystem/files.cpp create mode 100644 source/common/filesystem/files.h create mode 100644 source/common/filesystem/files_decompress.cpp create mode 100644 source/common/filesystem/fs_findfile.cpp create mode 100644 source/common/filesystem/fs_findfile.h create mode 100644 source/common/filesystem/fs_swap.h create mode 100644 source/common/textures/formats/qoitexture.cpp diff --git a/source/common/filesystem/files.cpp b/source/common/filesystem/files.cpp new file mode 100644 index 000000000..8865b7111 --- /dev/null +++ b/source/common/filesystem/files.cpp @@ -0,0 +1,494 @@ +/* +** files.cpp +** Implements classes for reading from files or memory blocks +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** Copyright 2005-2008 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 +#include "files.h" + +using namespace fs_private; + +#ifdef _WIN32 +std::wstring toWide(const char* str); +#endif + +FILE *myfopen(const char *filename, const char *flags) +{ +#ifndef _WIN32 + return fopen(filename, flags); +#else + auto widename = toWide(filename); + auto wideflags = toWide(flags); + return _wfopen(widename.c_str(), wideflags.c_str()); +#endif +} + + +//========================================================================== +// +// StdFileReader +// +// reads data from an stdio FILE* or part of it. +// +//========================================================================== + +class StdFileReader : public FileReaderInterface +{ + FILE *File = nullptr; + long StartPos = 0; + long FilePos = 0; + +public: + StdFileReader() + {} + + ~StdFileReader() + { + if (File != nullptr) + { + fclose(File); + } + File = nullptr; + } + + bool Open(const char *filename, long startpos = 0, long len = -1) + { + File = myfopen(filename, "rb"); + if (File == nullptr) return false; + FilePos = startpos; + StartPos = startpos; + Length = CalcFileLen(); + if (len >= 0 && len < Length) Length = len; + if (startpos > 0) Seek(0, SEEK_SET); + return true; + } + + long Tell() const override + { + return FilePos - StartPos; + } + + long Seek(long offset, int origin) override + { + if (origin == SEEK_SET) + { + offset += StartPos; + } + else if (origin == SEEK_CUR) + { + offset += FilePos; + } + else if (origin == SEEK_END) + { + offset += StartPos + Length; + } + if (offset < StartPos || offset > StartPos + Length) return -1; // out of scope + + if (0 == fseek(File, offset, SEEK_SET)) + { + FilePos = offset; + return 0; + } + return -1; + } + + long Read(void *buffer, long len) override + { + assert(len >= 0); + if (len <= 0) return 0; + if (FilePos + len > StartPos + Length) + { + len = Length - FilePos + StartPos; + } + len = (long)fread(buffer, 1, len, File); + FilePos += len; + return len; + } + + char *Gets(char *strbuf, int len) override + { + if (len <= 0 || FilePos >= StartPos + Length) return NULL; + char *p = fgets(strbuf, len, File); + if (p != NULL) + { + int old = FilePos; + FilePos = ftell(File); + if (FilePos - StartPos > Length) + { + strbuf[Length - old + StartPos] = 0; + } + } + return p; + } + +private: + long CalcFileLen() const + { + long endpos; + + fseek(File, 0, SEEK_END); + endpos = ftell(File); + fseek(File, 0, SEEK_SET); + return endpos; + } +}; + +//========================================================================== +// +// FileReaderRedirect +// +// like the above, but uses another File reader as its backing data +// +//========================================================================== + +class FileReaderRedirect : public FileReaderInterface +{ + FileReader *mReader = nullptr; + long StartPos = 0; + long FilePos = 0; + +public: + FileReaderRedirect(FileReader &parent, long start, long length) + { + mReader = &parent; + FilePos = start; + StartPos = start; + Length = length; + Seek(0, SEEK_SET); + } + + virtual long Tell() const override + { + return FilePos - StartPos; + } + + virtual long Seek(long offset, int origin) override + { + switch (origin) + { + case SEEK_SET: + offset += StartPos; + break; + + case SEEK_END: + offset += StartPos + Length; + break; + + case SEEK_CUR: + offset += (long)mReader->Tell(); + break; + } + if (offset < StartPos || offset > StartPos + Length) return -1; // out of scope + if (mReader->Seek(offset, FileReader::SeekSet) == 0) + { + FilePos = offset; + return 0; + } + return -1; + } + + virtual long Read(void *buffer, long len) override + { + assert(len >= 0); + if (len <= 0) return 0; + if (FilePos + len > StartPos + Length) + { + len = Length - FilePos + StartPos; + } + len = (long)mReader->Read(buffer, len); + FilePos += len; + return len; + } + + virtual char *Gets(char *strbuf, int len) override + { + if (len <= 0 || FilePos >= StartPos + Length) return NULL; + char *p = mReader->Gets(strbuf, len); + if (p != NULL) + { + int old = FilePos; + FilePos = (long)mReader->Tell(); + if (FilePos - StartPos > Length) + { + strbuf[Length - old + StartPos] = 0; + } + } + return p; + } + +}; + +//========================================================================== +// +// MemoryReader +// +// reads data from a block of memory +// +//========================================================================== + +long MemoryReader::Tell() const +{ + return FilePos; +} + +long MemoryReader::Seek(long offset, int origin) +{ + switch (origin) + { + case SEEK_CUR: + offset += FilePos; + break; + + case SEEK_END: + offset += Length; + break; + + } + if (offset < 0 || offset > Length) return -1; + FilePos = std::clamp(offset, 0, Length); + return 0; +} + +long MemoryReader::Read(void *buffer, long len) +{ + if (len>Length - FilePos) len = Length - FilePos; + if (len<0) len = 0; + memcpy(buffer, bufptr + FilePos, len); + FilePos += len; + return len; +} + +char *MemoryReader::Gets(char *strbuf, int len) +{ + if (len>Length - FilePos) len = Length - FilePos; + if (len <= 0) return NULL; + + char *p = strbuf; + while (len > 1) + { + if (bufptr[FilePos] == 0) + { + FilePos++; + break; + } + if (bufptr[FilePos] != '\r') + { + *p++ = bufptr[FilePos]; + len--; + if (bufptr[FilePos] == '\n') + { + FilePos++; + break; + } + } + FilePos++; + } + if (p == strbuf) return NULL; + *p++ = 0; + return strbuf; +} + +//========================================================================== +// +// MemoryArrayReader +// +// reads data from an array of memory +// +//========================================================================== + +class MemoryArrayReader : public MemoryReader +{ + std::vector buf; + +public: + MemoryArrayReader(const char *buffer, long length) + { + if (length > 0) + { + buf.resize(length); + memcpy(&buf[0], buffer, length); + } + UpdateBuffer(); + } + + std::vector &GetArray() { return buf; } + + void UpdateBuffer() + { + bufptr = (const char*)&buf[0]; + FilePos = 0; + Length = (long)buf.size(); + } +}; + + + +//========================================================================== +// +// FileReader +// +// this wraps the different reader types in an object with value semantics. +// +//========================================================================== + +bool FileReader::OpenFile(const char *filename, FileReader::Size start, FileReader::Size length) +{ + auto reader = new StdFileReader; + if (!reader->Open(filename, (long)start, (long)length)) + { + delete reader; + return false; + } + Close(); + mReader = reader; + return true; +} + +bool FileReader::OpenFilePart(FileReader &parent, FileReader::Size start, FileReader::Size length) +{ + auto reader = new FileReaderRedirect(parent, (long)start, (long)length); + Close(); + mReader = reader; + return true; +} + +bool FileReader::OpenMemory(const void *mem, FileReader::Size length) +{ + Close(); + mReader = new MemoryReader((const char *)mem, (long)length); + return true; +} + +bool FileReader::OpenMemoryArray(const void *mem, FileReader::Size length) +{ + Close(); + mReader = new MemoryArrayReader((const char *)mem, (long)length); + return true; +} + +bool FileReader::OpenMemoryArray(std::function&)> getter) +{ + auto reader = new MemoryArrayReader(nullptr, 0); + if (getter(reader->GetArray())) + { + Close(); + reader->UpdateBuffer(); + mReader = reader; + return true; + } + else + { + // This will keep the old buffer, if one existed + delete reader; + return false; + } +} + + +//========================================================================== +// +// FileWriter (the motivation here is to have a buffer writing subclass) +// +//========================================================================== + +bool FileWriter::OpenDirect(const char *filename) +{ + File = myfopen(filename, "wb"); + return (File != NULL); +} + +FileWriter *FileWriter::Open(const char *filename) +{ + FileWriter *fwrit = new FileWriter(); + if (fwrit->OpenDirect(filename)) + { + return fwrit; + } + delete fwrit; + return NULL; +} + +size_t FileWriter::Write(const void *buffer, size_t len) +{ + if (File != NULL) + { + return fwrite(buffer, 1, len, File); + } + else + { + return 0; + } +} + +long FileWriter::Tell() +{ + if (File != NULL) + { + return ftell(File); + } + else + { + return 0; + } +} + +long FileWriter::Seek(long offset, int mode) +{ + if (File != NULL) + { + return fseek(File, offset, mode); + } + else + { + return 0; + } +} + +size_t FileWriter::Printf(const char *fmt, ...) +{ + va_list arglist; + va_start(arglist, fmt); + auto n = snprintf(nullptr, 0, fmt, arglist); + std::string buf; + buf.resize(n); + snprintf(&buf.front(), n, fmt, arglist); + va_end(arglist); + return Write(buf.c_str(), n); +} + +size_t BufferWriter::Write(const void *buffer, size_t len) +{ + unsigned int ofs = mBuffer.Reserve((unsigned)len); + memcpy(&mBuffer[ofs], buffer, len); + return len; +} diff --git a/source/common/filesystem/files.h b/source/common/filesystem/files.h new file mode 100644 index 000000000..99a3b133f --- /dev/null +++ b/source/common/filesystem/files.h @@ -0,0 +1,398 @@ +/* +** files.h +** Implements classes for reading from files or memory blocks +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** Copyright 2005-2008 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. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef FILES_H +#define FILES_H + +#include +#include +#include +#include +#include +#include "fs_swap.h" + +#include "tarray.h" + +class FileSystemException : public std::exception +{ +protected: + static const int MAX_FSERRORTEXT = 1024; + char m_Message[MAX_FSERRORTEXT]; + +public: + FileSystemException(const char* error, ...) + { + va_list argptr; + va_start(argptr, error); + vsnprintf(m_Message, MAX_FSERRORTEXT, error, argptr); + va_end(argptr); + } + FileSystemException(const char* error, va_list argptr) + { + vsnprintf(m_Message, MAX_FSERRORTEXT, error, argptr); + } + char const* what() const noexcept override + { + return m_Message; + } +}; + +// Zip compression methods, extended by some internal types to be passed to OpenDecompressor +enum +{ + METHOD_STORED = 0, + METHOD_SHRINK = 1, + METHOD_IMPLODE = 6, + METHOD_DEFLATE = 8, + METHOD_BZIP2 = 12, + METHOD_LZMA = 14, + METHOD_PPMD = 98, + METHOD_LZSS = 1337, // not used in Zips - this is for Console Doom compression + METHOD_ZLIB = 1338, // Zlib stream with header, used by compressed nodes. + METHOD_TRANSFEROWNER = 0x8000, +}; + +class FileReader; + +class FileReaderInterface +{ +public: + long Length = -1; + virtual ~FileReaderInterface() {} + virtual long Tell () const = 0; + virtual long Seek (long offset, int origin) = 0; + virtual long Read (void *buffer, long len) = 0; + virtual char *Gets(char *strbuf, int len) = 0; + virtual const char *GetBuffer() const { return nullptr; } + long GetLength () const { return Length; } +}; + +class MemoryReader : public FileReaderInterface +{ +protected: + const char * bufptr = nullptr; + long FilePos = 0; + + MemoryReader() + {} + +public: + MemoryReader(const char *buffer, long length) + { + bufptr = buffer; + Length = length; + FilePos = 0; + } + + long Tell() const override; + long Seek(long offset, int origin) override; + long Read(void *buffer, long len) override; + char *Gets(char *strbuf, int len) override; + virtual const char *GetBuffer() const override { return bufptr; } +}; + + +struct FResourceLump; + +class FileReader +{ + friend struct FResourceLump; // needs access to the private constructor. + + FileReaderInterface *mReader = nullptr; + + FileReader(const FileReader &r) = delete; + FileReader &operator=(const FileReader &r) = delete; + +public: + + explicit FileReader(FileReaderInterface *r) + { + mReader = r; + } + + enum ESeek + { + SeekSet = SEEK_SET, + SeekCur = SEEK_CUR, + SeekEnd = SEEK_END + }; + + typedef ptrdiff_t Size; // let's not use 'long' here. + + FileReader() {} + + FileReader(FileReader &&r) + { + mReader = r.mReader; + r.mReader = nullptr; + } + + FileReader& operator =(FileReader &&r) + { + Close(); + mReader = r.mReader; + r.mReader = nullptr; + return *this; + } + + // This is for wrapping the actual reader for custom access where a managed FileReader won't work. + FileReaderInterface* GetInterface() + { + auto i = mReader; + mReader = nullptr; + return i; + } + + + ~FileReader() + { + Close(); + } + + bool isOpen() const + { + return mReader != nullptr; + } + + void Close() + { + if (mReader != nullptr) delete mReader; + mReader = nullptr; + } + + bool OpenFile(const char *filename, Size start = 0, Size length = -1); + bool OpenFilePart(FileReader &parent, Size start, Size length); + bool OpenMemory(const void *mem, Size length); // read directly from the buffer + bool OpenMemoryArray(const void *mem, Size length); // read from a copy of the buffer. + bool OpenMemoryArray(std::function&)> getter); // read contents to a buffer and return a reader to it + bool OpenDecompressor(FileReader &parent, Size length, int method, bool seekable, bool exceptions = false); // creates a decompressor stream. 'seekable' uses a buffered version so that the Seek and Tell methods can be used. + + Size Tell() const + { + return mReader->Tell(); + } + + Size Seek(Size offset, ESeek origin) + { + return mReader->Seek((long)offset, origin); + } + + Size Read(void *buffer, Size len) const + { + return mReader->Read(buffer, (long)len); + } + + std::vector Read(size_t len) + { + std::vector buffer(len); + Size length = mReader->Read(&buffer[0], (long)len); + buffer.resize((size_t)length); + return buffer; + } + + std::vector Read() + { + return Read(GetLength()); + } + + std::vector ReadPadded(size_t padding) + { + auto len = GetLength(); + std::vector buffer(len + padding); + Size length = mReader->Read(&buffer[0], (long)len); + if (length < len) buffer.clear(); + else memset(buffer.data() + len, 0, padding); + return buffer; + } + + char *Gets(char *strbuf, Size len) + { + return mReader->Gets(strbuf, (int)len); + } + + const char *GetBuffer() + { + return mReader->GetBuffer(); + } + + Size GetLength() const + { + return mReader->GetLength(); + } + + uint8_t ReadUInt8() + { + uint8_t v = 0; + Read(&v, 1); + return v; + } + + int8_t ReadInt8() + { + int8_t v = 0; + Read(&v, 1); + return v; + } + + + uint16_t ReadUInt16() + { + uint16_t v = 0; + Read(&v, 2); + return fs_private::LittleShort(v); + } + + int16_t ReadInt16() + { + return (int16_t)ReadUInt16(); + } + + int16_t ReadUInt16BE() + { + uint16_t v = 0; + Read(&v, 2); + return fs_private::BigShort(v); + } + + int16_t ReadInt16BE() + { + return (int16_t)ReadUInt16BE(); + } + + uint32_t ReadUInt32() + { + uint32_t v = 0; + Read(&v, 4); + return fs_private::LittleLong(v); + } + + int32_t ReadInt32() + { + return (int32_t)ReadUInt32(); + } + + uint32_t ReadUInt32BE() + { + uint32_t v = 0; + Read(&v, 4); + return fs_private::BigLong(v); + } + + int32_t ReadInt32BE() + { + return (int32_t)ReadUInt32BE(); + } + + uint64_t ReadUInt64() + { + uint64_t v = 0; + Read(&v, 8); + // Prove to me that there's a relevant 64 bit Big Endian architecture and I fix this! :P + return v; + } + + + friend class FileSystem; +}; + +class DecompressorBase : public FileReaderInterface +{ + bool exceptions = false; +public: + // These do not work but need to be defined to satisfy the FileReaderInterface. + // They will just error out when called. + long Tell() const override; + long Seek(long offset, int origin) override; + char* Gets(char* strbuf, int len) override; + void DecompressionError(const char* error, ...) const; + void SetOwnsReader(); + void EnableExceptions(bool on) { exceptions = on; } + +protected: + FileReader* File = nullptr; + FileReader OwnedFile; +}; + + +class FileWriter +{ +protected: + bool OpenDirect(const char *filename); + +public: + FileWriter(FILE *f = nullptr) // if passed, this writer will take over the file. + { + File = f; + } + virtual ~FileWriter() + { + Close(); + } + + static FileWriter *Open(const char *filename); + + virtual size_t Write(const void *buffer, size_t len); + virtual long Tell(); + virtual long Seek(long offset, int mode); + size_t Printf(const char *fmt, ...); + + virtual void Close() + { + if (File != NULL) fclose(File); + File = nullptr; + } + +protected: + + FILE *File; + +protected: + bool CloseOnDestruct; +}; + +class BufferWriter : public FileWriter +{ +protected: + TArray mBuffer; +public: + + BufferWriter() {} + virtual size_t Write(const void *buffer, size_t len) override; + TArray *GetBuffer() { return &mBuffer; } + TArray&& TakeBuffer() { return std::move(mBuffer); } +}; + + +#endif diff --git a/source/common/filesystem/files_decompress.cpp b/source/common/filesystem/files_decompress.cpp new file mode 100644 index 000000000..8b8f4958a --- /dev/null +++ b/source/common/filesystem/files_decompress.cpp @@ -0,0 +1,754 @@ +/* +** files.cpp +** Implements classes for reading from files or memory blocks +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** Copyright 2005-2008 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. +**--------------------------------------------------------------------------- +** +*/ + +// Caution: LzmaDec also pulls in windows.h! +#define NOMINMAX +#include "LzmaDec.h" +#include +#include +#include +#include + +#include "files.h" + +//========================================================================== +// +// DecompressionError +// +// Allows catching errors from the decompressor. The default is just to +// return failure from the calling function. +// +//========================================================================== + +void DecompressorBase::DecompressionError(const char *error, ...) const +{ + if (exceptions) + { + va_list argptr; + va_start(argptr, error); + throw FileSystemException(error, argptr); + va_end(argptr); + } +} + +long DecompressorBase::Tell () const +{ + DecompressionError("Cannot get position of decompressor stream"); + return 0; +} +long DecompressorBase::Seek (long offset, int origin) +{ + DecompressionError("Cannot seek in decompressor stream"); + return 0; +} +char *DecompressorBase::Gets(char *strbuf, int len) +{ + DecompressionError("Cannot use Gets on decompressor stream"); + return nullptr; +} + +void DecompressorBase::SetOwnsReader() +{ + OwnedFile = std::move(*File); + File = &OwnedFile; +} + +//========================================================================== +// +// +// +//========================================================================== + +static const char* ZLibError(int zerr) +{ + static const char* const errs[6] = { "Errno", "Stream Error", "Data Error", "Memory Error", "Buffer Error", "Version Error" }; + if (zerr >= 0 || zerr < -6) return "Unknown"; + else return errs[-zerr - 1]; +} + +//========================================================================== +// +// DecompressorZ +// +// The zlib wrapper +// reads data from a ZLib compressed stream +// +//========================================================================== + +class DecompressorZ : public DecompressorBase +{ + enum { BUFF_SIZE = 4096 }; + + bool SawEOF = false; + z_stream Stream; + uint8_t InBuff[BUFF_SIZE]; + +public: + bool Open (FileReader *file, bool zip) + { + if (File != nullptr) + { + DecompressionError("File already open"); + return false; + } + + int err; + + File = file; + FillBuffer (); + + Stream.zalloc = Z_NULL; + Stream.zfree = Z_NULL; + + if (!zip) err = inflateInit (&Stream); + else err = inflateInit2 (&Stream, -MAX_WBITS); + + if (err < Z_OK) + { + DecompressionError ("DecompressorZ: inflateInit failed: %s\n", ZLibError(err)); + return false; + } + return true; + } + + ~DecompressorZ () + { + inflateEnd (&Stream); + } + + long Read (void *buffer, long len) override + { + int err; + + if (File == nullptr) + { + DecompressionError("File not open"); + return 0; + } + + Stream.next_out = (Bytef *)buffer; + Stream.avail_out = len; + + do + { + err = inflate (&Stream, Z_SYNC_FLUSH); + if (Stream.avail_in == 0 && !SawEOF) + { + FillBuffer (); + } + } while (err == Z_OK && Stream.avail_out != 0); + + if (err != Z_OK && err != Z_STREAM_END) + { + DecompressionError ("Corrupt zlib stream"); + return 0; + } + + if (Stream.avail_out != 0) + { + DecompressionError ("Ran out of data in zlib stream"); + return 0; + } + + return len - Stream.avail_out; + } + + void FillBuffer () + { + auto numread = File->Read (InBuff, BUFF_SIZE); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + Stream.next_in = InBuff; + Stream.avail_in = (uInt)numread; + } +}; + + +//========================================================================== +// +// DecompressorZ +// +// The bzip2 wrapper +// reads data from a libbzip2 compressed stream +// +//========================================================================== + +class DecompressorBZ2; +static DecompressorBZ2 * stupidGlobal; // Why does that dumb global error callback not pass the decompressor state? + // Thanks to that brain-dead interface we have to use a global variable to get the error to the proper handler. + +class DecompressorBZ2 : public DecompressorBase +{ + enum { BUFF_SIZE = 4096 }; + + bool SawEOF = false; + bz_stream Stream; + uint8_t InBuff[BUFF_SIZE]; + +public: + bool Open(FileReader *file) + { + if (File != nullptr) + { + DecompressionError("File already open"); + return false; + } + + int err; + + File = file; + stupidGlobal = this; + FillBuffer (); + + Stream.bzalloc = NULL; + Stream.bzfree = NULL; + Stream.opaque = NULL; + + err = BZ2_bzDecompressInit(&Stream, 0, 0); + + if (err != BZ_OK) + { + DecompressionError ("DecompressorBZ2: bzDecompressInit failed: %d\n", err); + return false; + } + return true; + } + + ~DecompressorBZ2 () + { + stupidGlobal = this; + BZ2_bzDecompressEnd (&Stream); + } + + long Read (void *buffer, long len) override + { + if (File == nullptr) + { + DecompressionError("File not open"); + return 0; + } + + int err; + + stupidGlobal = this; + Stream.next_out = (char *)buffer; + Stream.avail_out = len; + + do + { + err = BZ2_bzDecompress(&Stream); + if (Stream.avail_in == 0 && !SawEOF) + { + FillBuffer (); + } + } while (err == BZ_OK && Stream.avail_out != 0); + + if (err != BZ_OK && err != BZ_STREAM_END) + { + DecompressionError ("Corrupt bzip2 stream"); + return 0; + } + + if (Stream.avail_out != 0) + { + DecompressionError ("Ran out of data in bzip2 stream"); + return 0; + } + + return len - Stream.avail_out; + } + + void FillBuffer () + { + auto numread = File->Read(InBuff, BUFF_SIZE); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + Stream.next_in = (char *)InBuff; + Stream.avail_in = (unsigned)numread; + } + +}; + +//========================================================================== +// +// bz_internal_error +// +// libbzip2 wants this, since we build it with BZ_NO_STDIO set. +// +//========================================================================== + +extern "C" void bz_internal_error (int errcode) +{ + if (stupidGlobal) stupidGlobal->DecompressionError("libbzip2: internal error number %d\n", errcode); + else std::terminate(); +} + +//========================================================================== +// +// DecompressorLZMA +// +// The lzma wrapper +// reads data from a LZMA compressed stream +// +//========================================================================== + +static void *SzAlloc(ISzAllocPtr, size_t size) { return malloc(size); } +static void SzFree(ISzAllocPtr, void *address) { free(address); } +ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +// Wraps around a Decompressor to decompress a lzma stream +class DecompressorLZMA : public DecompressorBase +{ + enum { BUFF_SIZE = 4096 }; + + bool SawEOF = false; + CLzmaDec Stream; + size_t Size; + size_t InPos, InSize; + size_t OutProcessed; + uint8_t InBuff[BUFF_SIZE]; + +public: + + bool Open(FileReader *file, size_t uncompressed_size) + { + if (File != nullptr) + { + DecompressionError("File already open"); + return false; + } + + uint8_t header[4 + LZMA_PROPS_SIZE]; + int err; + File = file; + + Size = uncompressed_size; + OutProcessed = 0; + + // Read zip LZMA properties header + if (File->Read(header, sizeof(header)) < (long)sizeof(header)) + { + DecompressionError("DecompressorLZMA: File too short\n"); + return false; + } + if (header[2] + header[3] * 256 != LZMA_PROPS_SIZE) + { + DecompressionError("DecompressorLZMA: LZMA props size is %d (expected %d)\n", + header[2] + header[3] * 256, LZMA_PROPS_SIZE); + return false; + } + + FillBuffer(); + + LzmaDec_Construct(&Stream); + err = LzmaDec_Allocate(&Stream, header + 4, LZMA_PROPS_SIZE, &g_Alloc); + + if (err != SZ_OK) + { + DecompressionError("DecompressorLZMA: LzmaDec_Allocate failed: %d\n", err); + return false; + } + + LzmaDec_Init(&Stream); + return true; + } + + ~DecompressorLZMA () + { + LzmaDec_Free(&Stream, &g_Alloc); + } + + long Read (void *buffer, long len) override + { + if (File == nullptr) + { + DecompressionError("File not open"); + return 0; + } + + int err; + Byte *next_out = (Byte *)buffer; + + do + { + ELzmaFinishMode finish_mode = LZMA_FINISH_ANY; + ELzmaStatus status; + size_t out_processed = len; + size_t in_processed = InSize; + + err = LzmaDec_DecodeToBuf(&Stream, next_out, &out_processed, InBuff + InPos, &in_processed, finish_mode, &status); + InPos += in_processed; + InSize -= in_processed; + next_out += out_processed; + len = (long)(len - out_processed); + if (err != SZ_OK) + { + DecompressionError ("Corrupt LZMA stream"); + return 0; + } + if (in_processed == 0 && out_processed == 0) + { + if (status != LZMA_STATUS_FINISHED_WITH_MARK) + { + DecompressionError ("Corrupt LZMA stream"); + return 0; + } + } + if (InSize == 0 && !SawEOF) + { + FillBuffer (); + } + } while (err == SZ_OK && len != 0); + + if (err != Z_OK && err != Z_STREAM_END) + { + DecompressionError ("Corrupt LZMA stream"); + return 0; + } + + if (len != 0) + { + DecompressionError ("Ran out of data in LZMA stream"); + return 0; + } + + return (long)(next_out - (Byte *)buffer); + } + + void FillBuffer () + { + auto numread = File->Read(InBuff, BUFF_SIZE); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + InPos = 0; + InSize = numread; + } + +}; + +//========================================================================== +// +// Console Doom LZSS wrapper. +// +//========================================================================== + +class DecompressorLZSS : public DecompressorBase +{ + enum { BUFF_SIZE = 4096, WINDOW_SIZE = 4096, INTERNAL_BUFFER_SIZE = 128 }; + + bool SawEOF; + uint8_t InBuff[BUFF_SIZE]; + + enum StreamState + { + STREAM_EMPTY, + STREAM_BITS, + STREAM_FLUSH, + STREAM_FINAL + }; + struct + { + StreamState State; + + uint8_t *In; + unsigned int AvailIn; + unsigned int InternalOut; + + uint8_t CFlags, Bits; + + uint8_t Window[WINDOW_SIZE+INTERNAL_BUFFER_SIZE]; + const uint8_t *WindowData; + uint8_t *InternalBuffer; + } Stream; + + void FillBuffer() + { + if(Stream.AvailIn) + memmove(InBuff, Stream.In, Stream.AvailIn); + + auto numread = File->Read(InBuff+Stream.AvailIn, BUFF_SIZE-Stream.AvailIn); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + Stream.In = InBuff; + Stream.AvailIn = (unsigned)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; + + uint16_t pos = fs_private::BigShort(*(uint16_t*)Stream.In); + uint8_t 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 uint8_t* 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 = std::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: + bool Open(FileReader *file) + { + if (File != nullptr) + { + DecompressionError("File already open"); + return false; + } + + File = file; + Stream.State = STREAM_EMPTY; + Stream.WindowData = Stream.InternalBuffer = Stream.Window+WINDOW_SIZE; + Stream.InternalOut = 0; + Stream.AvailIn = 0; + + FillBuffer(); + return true; + } + + ~DecompressorLZSS() + { + } + + long Read(void *buffer, long len) override + { + + uint8_t *Out = (uint8_t*)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 = std::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 (long)(Out - (uint8_t*)buffer); + } +}; + + +bool FileReader::OpenDecompressor(FileReader &parent, Size length, int method, bool seekable, bool exceptions) +{ + DecompressorBase* dec = nullptr; + try + { + FileReader* p = &parent; + switch (method & ~METHOD_TRANSFEROWNER) + { + case METHOD_DEFLATE: + case METHOD_ZLIB: + { + auto idec = new DecompressorZ; + dec = idec; + idec->EnableExceptions(exceptions); + if (!idec->Open(p, method == METHOD_DEFLATE)) + { + delete idec; + return false; + } + break; + } + case METHOD_BZIP2: + { + auto idec = new DecompressorBZ2; + dec = idec; + idec->EnableExceptions(exceptions); + if (!idec->Open(p)) + { + delete idec; + return false; + } + break; + } + case METHOD_LZMA: + { + auto idec = new DecompressorLZMA; + dec = idec; + idec->EnableExceptions(exceptions); + if (!idec->Open(p, length)) + { + delete idec; + return false; + } + break; + } + case METHOD_LZSS: + { + auto idec = new DecompressorLZSS; + dec = idec; + idec->EnableExceptions(exceptions); + if (!idec->Open(p)) + { + delete idec; + return false; + } + break; + } + + // todo: METHOD_IMPLODE, METHOD_SHRINK + default: + return false; + } + if (method & METHOD_TRANSFEROWNER) + { + dec->SetOwnsReader(); + } + + dec->Length = (long)length; + if (!seekable) + { + Close(); + mReader = dec; + return true; + } + else + { + // todo: create a wrapper. for now this fails + delete dec; + return false; + } + } + catch (...) + { + if (dec) delete dec; + throw; + } +} + diff --git a/source/common/filesystem/filesystem.h b/source/common/filesystem/filesystem.h index da1bd95bb..adc3706d3 100644 --- a/source/common/filesystem/filesystem.h +++ b/source/common/filesystem/filesystem.h @@ -128,7 +128,6 @@ public: std::vector GetFileData(int lump, int pad = 0); // reads lump into a writable buffer and optionally adds some padding at the end. (FileData isn't writable!) std::vector GetFileData(const char* name, int pad = 0) { return GetFileData(GetNumForName(name), pad); } FileData ReadFile (int lump); - FileData ReadFile (const char *name) { return ReadFile (GetNumForName (name)); } inline std::vector LoadFile(const char* name, int padding = 0) { diff --git a/source/common/filesystem/fs_findfile.cpp b/source/common/filesystem/fs_findfile.cpp new file mode 100644 index 000000000..ed5f14258 --- /dev/null +++ b/source/common/filesystem/fs_findfile.cpp @@ -0,0 +1,389 @@ +/* +** findfile.cpp +** Wrapper around the native directory scanning APIs +** +**--------------------------------------------------------------------------- +** Copyright 1998-2016 Randy Heit +** Copyright 2005-2020 Christoph Oelckers +** +** 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 "fs_findfile.h" +#include + + +enum +{ + ZPATH_MAX = 260 +}; + +#ifndef _WIN32 + + +#else + + +enum +{ + FA_RDONLY = 1, + FA_HIDDEN = 2, + FA_SYSTEM = 4, + FA_DIREC = 16, + FA_ARCH = 32, +}; + +#endif + + +#ifndef _WIN32 + +#include +#include +#include + +#include + +struct findstate_t +{ + std::string path; + struct dirent** namelist; + int current; + int count; +}; + +static const char* FS_FindName(findstate_t* fileinfo) +{ + return (fileinfo->namelist[fileinfo->current]->d_name); +} + +enum +{ + FA_RDONLY = 1, + FA_HIDDEN = 2, + FA_SYSTEM = 4, + FA_DIREC = 8, + FA_ARCH = 16, +}; + + +static const char *pattern; + +static int matchfile(const struct dirent *ent) +{ + return fnmatch(pattern, ent->d_name, FNM_NOESCAPE) == 0; +} + +static void *FS_FindFirst(const char *const filespec, findstate_t *const fileinfo) +{ + const char* dir; + + const char *const slash = strrchr(filespec, '/'); + + if (slash) + { + pattern = slash + 1; + fileinfo->path = std::string(filespec, 0, slash - filespec + 1); + dir = fileinfo->path.c_str(); + } + else + { + pattern = filespec; + dir = "."; + } + + fileinfo->current = 0; + fileinfo->count = scandir(dir, &fileinfo->namelist, matchfile, alphasort); + + if (fileinfo->count > 0) + { + return fileinfo; + } + + return (void *)-1; +} + +static int FS_FindNext(void *const handle, findstate_t *const fileinfo) +{ + findstate_t *const state = static_cast(handle); + + if (state->current < fileinfo->count) + { + return ++state->current < fileinfo->count ? 0 : -1; + } + + return -1; +} + +static int FS_FindClose(void *const handle) +{ + findstate_t *const state = static_cast(handle); + + if (handle != (void *)-1 && state->count > 0) + { + for (int i = 0; i < state->count; ++i) + { + free(state->namelist[i]); + } + + free(state->namelist); + state->namelist = nullptr; + state->count = 0; + } + + return 0; +} + +static bool DirEntryExists(const char* pathname, bool* isdir) +{ + if (isdir) *isdir = false; + struct stat info; + bool res = stat(pathname, &info) == 0; + if (isdir) *isdir = !!(info.st_mode & S_IFDIR); + return res; +} + +static int FS_FindAttr(findstate_t *const fileinfo) +{ + dirent *const ent = fileinfo->namelist[fileinfo->current]; + const std::string path = fileinfo->path + ent->d_name; + bool isdir; + + if (DirEntryExists(path.c_str(), &isdir)) + { + return isdir ? FA_DIREC : 0; + } + + return 0; +} + +std::string FS_FullPath(const char* directory) +{ + // todo + return directory +} + +static size_t FS_GetFileSize(findstate_t* handle, const char* pathname) +{ + struct stat info; + bool res = stat(pathname, &info) == 0; + if (!res || (info.st_mode & S_IFDIR)) return 0; + return (size_t)info.st_size; +} + + +#else + +#include +#include + +std::wstring toWide(const char* str) +{ + int len = (int)strlen(str); + int size_needed = MultiByteToWideChar(CP_UTF8, 0, str, len, nullptr, 0); + std::wstring wide; + wide.resize(size_needed); + MultiByteToWideChar(CP_UTF8, 0, str, len, &wide.front(), size_needed); + return wide; +} + +static std::string toUtf8(const wchar_t* str) +{ + auto len = wcslen(str); + int size_needed = WideCharToMultiByte(CP_UTF8, 0, str, (int)len, nullptr, 0, nullptr, nullptr); + std::string utf8; + utf8.resize(size_needed); + WideCharToMultiByte(CP_UTF8, 0, str, (int)len, &utf8.front(), size_needed, nullptr, nullptr); + return utf8; +} + +struct findstate_t +{ + struct FileTime + { + uint32_t lo, hi; + }; + // Mirror WIN32_FIND_DATAW in . We cannot pull in the Windows header here as it would pollute all consumers' symbol space. + struct WinData + { + uint32_t Attribs; + FileTime Times[3]; + uint32_t Size[2]; + uint32_t Reserved[2]; + wchar_t Name[ZPATH_MAX]; + wchar_t AltName[14]; + }; + WinData FindData; + std::string UTF8Name; +}; + + +static int FS_FindAttr(findstate_t* fileinfo) +{ + return fileinfo->FindData.Attribs; +} + +//========================================================================== +// +// FS_FindFirst +// +// Start a pattern matching sequence. +// +//========================================================================== + + +static void *FS_FindFirst(const char *filespec, findstate_t *fileinfo) +{ + static_assert(sizeof(WIN32_FIND_DATAW) == sizeof(fileinfo->FindData), "FindData size mismatch"); + return FindFirstFileW(toWide(filespec).c_str(), (LPWIN32_FIND_DATAW)&fileinfo->FindData); +} + +//========================================================================== +// +// FS_FindNext +// +// Return the next file in a pattern matching sequence. +// +//========================================================================== + +static int FS_FindNext(void *handle, findstate_t *fileinfo) +{ + return FindNextFileW((HANDLE)handle, (LPWIN32_FIND_DATAW)&fileinfo->FindData) == 0; +} + +//========================================================================== +// +// FS_FindClose +// +// Finish a pattern matching sequence. +// +//========================================================================== + +static int FS_FindClose(void *handle) +{ + return FindClose((HANDLE)handle); +} + +//========================================================================== +// +// FS_FindName +// +// Returns the name for an entry +// +//========================================================================== + +static const char *FS_FindName(findstate_t *fileinfo) +{ + fileinfo->UTF8Name = toUtf8(fileinfo->FindData.Name); + return fileinfo->UTF8Name.c_str(); +} + +std::string FS_FullPath(const char* directory) +{ + auto wdirectory = _wfullpath(nullptr, toWide(directory).c_str(), _MAX_PATH); + std::string sdirectory = toUtf8(wdirectory); + free((void*)wdirectory); + for (auto& c : sdirectory) if (c == '\\') c = '/'; + return sdirectory; +} + +static size_t FS_GetFileSize(findstate_t* handle, const char* pathname) +{ + return handle->FindData.Size[0] + ((uint64_t)handle->FindData.Size[1] << 32); +} + +#endif + + +//========================================================================== +// +// ScanDirectory +// +//========================================================================== + +static bool ScanDirectory(FileList& list, const char* dirpath, const char* match, const char* relpath, bool nosubdir, bool readhidden) +{ + findstate_t find; + + std::string dirpathn = dirpath; + FixPathSeparator(&dirpathn.front()); + if (dirpathn[dirpathn.length() - 1] != '/') dirpathn += '/'; + + std::string dirmatch = dirpath; + dirmatch += match; + + auto handle = FS_FindFirst(dirmatch.c_str(), &find); + if (handle == ((void*)(-1))) return false; + FileListEntry fl; + do + { + auto attr = FS_FindAttr(&find); + if (!readhidden && (attr & FA_HIDDEN)) + { + // Skip hidden files and directories. (Prevents SVN/git bookkeeping info from being included.) + continue; + } + auto fn = FS_FindName(&find); + + if (attr & FA_DIREC) + { + if (fn[0] == '.' && + (fn[1] == '\0' || + (fn[1] == '.' && fn[2] == '\0'))) + { + // Do not record . and .. directories. + continue; + } + } + + fl.FileName = fn; + fl.FilePath = dirpathn + fn; + fl.FilePathRel = relpath; + fl.FilePathRel += '/'; + fl.FilePathRel += fn; + fl.isDirectory = !!(attr & FA_DIREC); + fl.isReadonly = !!(attr & FA_RDONLY); + fl.isHidden = !!(attr & FA_HIDDEN); + fl.isSystem = !!(attr & FA_SYSTEM); + fl.Length = FS_GetFileSize(&find, fl.FilePath.c_str()); + list.push_back(fl); + if (!nosubdir && (attr & FA_DIREC)) + { + std::string rel = relpath + '/' + fl.FileName; + ScanDirectory(list, fl.FilePath.c_str(), match); + fl.Length = 0; + } + } while (FS_FindNext(handle, &find) == 0); + FS_FindClose(handle); + return true; +} + + +bool ScanDirectory(std::vector& list, const char* dirpath, const char* match, bool nosubdir, bool readhidden) +{ + return ScanDirectory(list, dirpath, match, "", nosubdir, readhidden); +} + diff --git a/source/common/filesystem/fs_findfile.h b/source/common/filesystem/fs_findfile.h new file mode 100644 index 000000000..85d328d43 --- /dev/null +++ b/source/common/filesystem/fs_findfile.h @@ -0,0 +1,35 @@ +#pragma once +// Directory searching routines + +#include +#include +#include + +struct FileListEntry +{ + std::string FileName; // file name only + std::string FilePath; // full path to file + std::string FilePathRel; // path relative to the scanned directory. + size_t Length = 0; + bool isDirectory = false; + bool isReadonly = false; + bool isHidden = false; + bool isSystem = false; +}; + +using FileList = std::vector; + +struct FCompressedBuffer; +bool ScanDirectory(std::vector& list, const char* dirpath, const char* match, bool nosubdir = false, bool readhidden = false); +bool WriteZip(const char* filename, const FCompressedBuffer* content, size_t contentcount); + +inline void FixPathSeparator(char* path) +{ + while (*path) + { + if (*path == '\\') + *path = '/'; + path++; + } +} + diff --git a/source/common/filesystem/fs_swap.h b/source/common/filesystem/fs_swap.h new file mode 100644 index 000000000..1e8bbef83 --- /dev/null +++ b/source/common/filesystem/fs_swap.h @@ -0,0 +1,121 @@ +// +// DESCRIPTION: +// Endianess handling, swapping 16bit and 32bit. +// +//----------------------------------------------------------------------------- + + +#ifndef __FS_SWAP_H__ +#define __FS_SWAP_H__ + +#include + +// Endianess handling. +// WAD files are stored little endian. + +#ifdef __APPLE__ +#include +#endif + +namespace fs_private +{ + +#ifdef __APPLE__ + +inline unsigned short LittleShort(unsigned short x) +{ + return OSSwapLittleToHostInt16(x); +} + +inline unsigned int LittleLong(unsigned int x) +{ + return OSSwapLittleToHostInt32(x); +} + +inline unsigned short BigShort(unsigned short x) +{ + return OSSwapBigToHostInt16(x); +} + +inline unsigned int BigLong(unsigned int x) +{ + return OSSwapBigToHostInt32(x); +} + + +#elif defined __BIG_ENDIAN__ + +// Swap 16bit, that is, MSB and LSB byte. +// No masking with 0xFF should be necessary. +inline unsigned short LittleShort (unsigned short x) +{ + return (unsigned short)((x>>8) | (x<<8)); +} + +inline unsigned int LittleLong (unsigned int x) +{ + return (unsigned int)( + (x>>24) + | ((x>>8) & 0xff00) + | ((x<<8) & 0xff0000) + | (x<<24)); +} + +inline unsigned short BigShort(unsigned short x) +{ + return x; +} + +inline unsigned int BigLong(unsigned int x) +{ + return x; +} + +#else + +inline unsigned short LittleShort(unsigned short x) +{ + return x; +} + +inline unsigned int LittleLong(unsigned int x) +{ + return x; +} + +#ifdef _MSC_VER + +inline unsigned short BigShort(unsigned short x) +{ + return _byteswap_ushort(x); +} + +inline unsigned int BigLong(unsigned int x) +{ + return (unsigned int)_byteswap_ulong((unsigned long)x); +} + +#pragma warning (default: 4035) + +#else + +inline unsigned short BigShort (unsigned short x) +{ + return (unsigned short)((x>>8) | (x<<8)); +} + +inline unsigned int BigLong (unsigned int x) +{ + return (unsigned int)( + (x>>24) + | ((x>>8) & 0xff00) + | ((x<<8) & 0xff0000) + | (x<<24)); +} + +#endif + +#endif // __BIG_ENDIAN__ +} + +#endif // __M_SWAP_H__ diff --git a/source/common/textures/formats/qoitexture.cpp b/source/common/textures/formats/qoitexture.cpp new file mode 100644 index 000000000..223a0b70e --- /dev/null +++ b/source/common/textures/formats/qoitexture.cpp @@ -0,0 +1,213 @@ +/* +** qoitexture.cpp +** Texture class for QOI (Quite OK Image Format) images +** +**--------------------------------------------------------------------------- +** Copyright 2023 Cacodemon345 +** Copyright 2022 Dominic Szablewski +** All rights reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in all +** copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include "files.h" +#include "filesystem.h" +#include "bitmap.h" +#include "imagehelpers.h" +#include "image.h" + +#pragma pack(1) + +struct QOIHeader +{ + char magic[4]; + uint32_t width; + uint32_t height; + uint8_t channels; + uint8_t colorspace; +}; +#pragma pack() + +class FQOITexture : public FImageSource +{ +public: + FQOITexture(int lumpnum, QOIHeader& header); + PalettedPixels CreatePalettedPixels(int conversion) override; + int CopyPixels(FBitmap *bmp, int conversion) override; +}; + +FImageSource *QOIImage_TryCreate(FileReader &file, int lumpnum) +{ + QOIHeader header; + + if (file.GetLength() < (sizeof(header) + 8)) + { + return nullptr; + } + + file.Seek(0, FileReader::SeekSet); + file.Read((void *)&header, sizeof(header)); + + if (header.magic[0] != 'q' || header.magic[1] != 'o' || header.magic[2] != 'i' || header.magic[3] != 'f') + { + return nullptr; + } + + if (header.width == 0 || header.height == 0 || header.channels < 3 || header.channels > 4 || header.colorspace > 1) + { + return nullptr; + } + + return new FQOITexture(lumpnum, header); +} + +FQOITexture::FQOITexture(int lumpnum, QOIHeader& header) + : FImageSource(lumpnum) +{ + LeftOffset = TopOffset = 0; + Width = header.width; + Height = header.height; + if (header.channels == 3) bMasked = bTranslucent = false; +} + +PalettedPixels FQOITexture::CreatePalettedPixels(int conversion) +{ + FBitmap bitmap; + bitmap.Create(Width, Height); + CopyPixels(&bitmap, conversion); + const uint8_t *data = bitmap.GetPixels(); + + uint8_t *dest_p; + int dest_adv = Height; + int dest_rew = Width * Height - 1; + + PalettedPixels Pixels(Width * Height); + dest_p = Pixels.Data(); + + bool doalpha = conversion == luminance; + // Convert the source image from row-major to column-major format and remap it + for (int y = Height; y != 0; --y) + { + for (int x = Width; x != 0; --x) + { + int b = *data++; + int g = *data++; + int r = *data++; + int a = *data++; + if (a < 128) + *dest_p = 0; + else + *dest_p = ImageHelpers::RGBToPalette(doalpha, r, g, b); + dest_p += dest_adv; + } + dest_p -= dest_rew; + } + return Pixels; +} + +int FQOITexture::CopyPixels(FBitmap *bmp, int conversion) +{ + enum + { + QOI_OP_INDEX = 0x00, /* 00xxxxxx */ + QOI_OP_DIFF = 0x40, /* 01xxxxxx */ + QOI_OP_LUMA = 0x80, /* 10xxxxxx */ + QOI_OP_RUN = 0xc0, /* 11xxxxxx */ + QOI_OP_RGB = 0xfe, /* 11111110 */ + QOI_OP_RGBA = 0xff, /* 11111111 */ + + QOI_MASK_2 = 0xc0, /* 11000000 */ + }; + + constexpr auto QOI_COLOR_HASH = [](PalEntry C) { return (C.r * 3 + C.g * 5 + C.b * 7 + C.a * 11); }; + + auto lump = fileSystem.OpenFileReader(SourceLump); + auto bytes = lump.Read(); + if (bytes.size() < 22) return 0; // error + PalEntry index[64] = {}; + PalEntry pe = 0xff000000; + + size_t p = 14, run = 0; + + size_t chunks_len = bytes.size() - 8; + + for (int h = 0; h < Height; h++) + { + auto pixels = bmp->GetPixels() + h * bmp->GetPitch(); + for (int w = 0; w < Width; w++) + { + if (run > 0) + { + run--; + } + else if (p < chunks_len) + { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) + { + pe.r = bytes[p++]; + pe.g = bytes[p++]; + pe.b = bytes[p++]; + } + else if (b1 == QOI_OP_RGBA) + { + pe.r = bytes[p++]; + pe.g = bytes[p++]; + pe.b = bytes[p++]; + pe.a = bytes[p++]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) + { + pe = index[b1]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) + { + pe.r += ((b1 >> 4) & 0x03) - 2; + pe.g += ((b1 >> 2) & 0x03) - 2; + pe.b += (b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) + { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + pe.r += vg - 8 + ((b2 >> 4) & 0x0f); + pe.g += vg; + pe.b += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) + { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(pe) % 64] = pe; + } + } + + pixels[0] = pe.b; + pixels[1] = pe.g; + pixels[2] = pe.r; + pixels[3] = pe.a; + pixels += 4; + } + return bMasked? -1 : 0; +} diff --git a/source/games/duke/src/sounds.cpp b/source/games/duke/src/sounds.cpp index 5c4123eb2..6febd4c6c 100644 --- a/source/games/duke/src/sounds.cpp +++ b/source/games/duke/src/sounds.cpp @@ -85,7 +85,6 @@ class DukeSoundEngine : public RazeSoundEngine { // client specific parts of the sound engine go in this class. void CalcPosVel(int type, const void* source, const float pt[3], int channum, int chanflags, FSoundID chanSound, FVector3* pos, FVector3* vel, FSoundChan* chan) override; - std::vector ReadSound(int lumpnum) override; public: DukeSoundEngine() diff --git a/source/games/sw/src/sounds.cpp b/source/games/sw/src/sounds.cpp index b7409e7c1..cb741bf8e 100644 --- a/source/games/sw/src/sounds.cpp +++ b/source/games/sw/src/sounds.cpp @@ -405,18 +405,6 @@ public: }; -//========================================================================== -// -// -// -//========================================================================== - -std::vector SWSoundEngine::ReadSound(int lumpnum) -{ - auto wlump = fileSystem.OpenFileReader(lumpnum); - return wlump.Read(); -} - //========================================================================== // //