/* * This file is part of the DXX-Rebirth project . * It is copyright by its individual contributors, as recorded in the * project's Git history. See COPYING.txt at the top level for license * terms and a link to the Git history. */ /* * * Some simple physfs extensions * */ #pragma once #include #include #include #include #include #include #include // When PhysicsFS can *easily* be built as a framework on Mac OS X, // the framework form will be supported again -kreatordxx #if 1 //!(defined(__APPLE__) && defined(__MACH__)) #include #else #include #endif #include "fmtcheck.h" #include "dxxsconf.h" #include "dsx-ns.h" #include "dxxerror.h" #include "vecmat.h" #include "byteutil.h" #ifdef __cplusplus #include #include "u_mem.h" #include "pack.h" #include "ntstring.h" #include "fwd-partial_range.h" #include #include #ifdef DXX_CONSTANT_TRUE #define _DXX_PHYSFS_CHECK_SIZE_CONSTANT(S,v) DXX_CONSTANT_TRUE((S) > (v)) #define _DXX_PHYSFS_CHECK_SIZE(S,C,v) _DXX_PHYSFS_CHECK_SIZE_CONSTANT(static_cast(S) * static_cast(C), v) #define DXX_PHYSFS_CHECK_READ_SIZE_OBJECT_SIZE(S,C,v) \ (void)(__builtin_object_size(v, 1) != static_cast(-1) && _DXX_PHYSFS_CHECK_SIZE(S,C,__builtin_object_size(v, 1)) && (DXX_ALWAYS_ERROR_FUNCTION(dxx_trap_overwrite, "read size exceeds element size"), 0)) #define DXX_PHYSFS_CHECK_READ_SIZE_ARRAY_SIZE(S,C) \ (void)(_DXX_PHYSFS_CHECK_SIZE(S,C,sizeof(v)) && (DXX_ALWAYS_ERROR_FUNCTION(dxx_trap_overwrite, "read size exceeds array size"), 0)) #define DXX_PHYSFS_CHECK_WRITE_SIZE_OBJECT_SIZE(S,C,v) \ (void)(__builtin_object_size(v, 1) != static_cast(-1) && _DXX_PHYSFS_CHECK_SIZE(S,C,__builtin_object_size(v, 1)) && (DXX_ALWAYS_ERROR_FUNCTION(dxx_trap_overwrite, "write size exceeds element size"), 0)) #define DXX_PHYSFS_CHECK_WRITE_ELEMENT_SIZE_CONSTANT(S,C) \ ((void)(dxx_builtin_constant_p(S) || dxx_builtin_constant_p(C) || \ (DXX_ALWAYS_ERROR_FUNCTION(dxx_trap_nonconstant_size, "array element size is not constant"), 0))) #define DXX_PHYSFS_CHECK_WRITE_SIZE_ARRAY_SIZE(S,C) \ ((void)(_DXX_PHYSFS_CHECK_SIZE(S,C,sizeof(v)) && \ (DXX_ALWAYS_ERROR_FUNCTION(dxx_trap_overread, "write size exceeds array size"), 0))) #else #define DXX_PHYSFS_CHECK_READ_SIZE_OBJECT_SIZE(S,C,v) ((void)0) #define DXX_PHYSFS_CHECK_READ_SIZE_ARRAY_SIZE(S,C) ((void)0) #define DXX_PHYSFS_CHECK_WRITE_SIZE_OBJECT_SIZE(S,C,v) ((void)0) #define DXX_PHYSFS_CHECK_WRITE_ELEMENT_SIZE_CONSTANT(S,C) ((void)0) #define DXX_PHYSFS_CHECK_WRITE_SIZE_ARRAY_SIZE(S,C) ((void)0) #endif #define DXX_PHYSFS_CHECK_WRITE_CONSTANTS(S,C) \ ((void)(DXX_PHYSFS_CHECK_WRITE_ELEMENT_SIZE_CONSTANT(S,C), \ DXX_PHYSFS_CHECK_WRITE_SIZE_ARRAY_SIZE(S,C), 0)) \ namespace dcx { template __attribute_always_inline() static inline PHYSFS_sint64 PHYSFSX_check_read(PHYSFS_File *file, V *v, const PHYSFS_uint32 S, const PHYSFS_uint32 C) { static_assert(std::is_standard_layout::value && std::is_trivial::value, "non-POD value read"); DXX_PHYSFS_CHECK_READ_SIZE_OBJECT_SIZE(S, C, v); return PHYSFS_read(file, v, S, C); } template __attribute_always_inline() static inline PHYSFS_sint64 PHYSFSX_check_read(PHYSFS_File *file, std::array &v, PHYSFS_uint32 S, PHYSFS_uint32 C) { static_assert(std::is_standard_layout::value && std::is_trivial::value, "C++ array of non-POD elements read"); DXX_PHYSFS_CHECK_READ_SIZE_ARRAY_SIZE(S, C); return PHYSFSX_check_read(file, &v[0], S, C); } template __attribute_always_inline() static inline PHYSFS_sint64 PHYSFSX_check_read(PHYSFS_File *file, const std::unique_ptr &v, PHYSFS_uint32 S, PHYSFS_uint32 C) { return PHYSFSX_check_read(file, v.get(), S, C); } template __attribute_always_inline() static inline PHYSFS_sint64 PHYSFSX_check_write(PHYSFS_File *file, const V *v, const PHYSFS_uint32 S, const PHYSFS_uint32 C) { static_assert(std::is_standard_layout::value && std::is_trivial::value, "non-POD value written"); if constexpr (std::is_integral::value) DXX_PHYSFS_CHECK_WRITE_ELEMENT_SIZE_CONSTANT(S,C); DXX_PHYSFS_CHECK_WRITE_SIZE_OBJECT_SIZE(S, C, v); return PHYSFS_write(file, v, S, C); } template __attribute_always_inline() static inline PHYSFS_sint64 PHYSFSX_check_write(PHYSFS_File *file, const std::array &v, PHYSFS_uint32 S, PHYSFS_uint32 C) { static_assert(std::is_standard_layout::value && std::is_trivial::value, "C++ array of non-POD elements written"); DXX_PHYSFS_CHECK_WRITE_CONSTANTS(S,C); return PHYSFSX_check_write(file, &v[0], S, C); } template __attribute_always_inline() static inline PHYSFS_sint64 PHYSFSX_check_write(PHYSFS_File *file, const std::unique_ptr &p, PHYSFS_uint32 S, PHYSFS_uint32 C) { return PHYSFSX_check_write(file, p.get(), S, C); } template PHYSFS_sint64 PHYSFSX_check_read(PHYSFS_File *file, exact_type v, PHYSFS_uint32 S, PHYSFS_uint32 C) = delete; template PHYSFS_sint64 PHYSFSX_check_write(PHYSFS_File *file, exact_type v, PHYSFS_uint32 S, PHYSFS_uint32 C) = delete; template PHYSFS_sint64 PHYSFSX_check_read(PHYSFS_File *file, V **v, PHYSFS_uint32 S, PHYSFS_uint32 C) = delete; template PHYSFS_sint64 PHYSFSX_check_write(PHYSFS_File *file, V **v, PHYSFS_uint32 S, PHYSFS_uint32 C) = delete; #define PHYSFS_read(F,V,S,C) PHYSFSX_check_read(F,V,S,C) #define PHYSFS_write(F,V,S,C) PHYSFSX_check_write(F,V,S,C) static inline PHYSFS_sint16 PHYSFSX_readSXE16(PHYSFS_File *file, int swap) { PHYSFS_sint16 val; PHYSFS_read(file, &val, sizeof(val), 1); return swap ? SWAPSHORT(val) : val; } static inline PHYSFS_sint32 PHYSFSX_readSXE32(PHYSFS_File *file, int swap) { PHYSFS_sint32 val; PHYSFS_read(file, &val, sizeof(val), 1); return swap ? SWAPINT(val) : val; } static inline int PHYSFSX_writeU8(PHYSFS_File *file, PHYSFS_uint8 val) { return PHYSFS_write(file, &val, 1, 1); } static inline int PHYSFSX_writeString(PHYSFS_File *file, const char *s) { return PHYSFS_write(file, s, 1, strlen(s) + 1); } static inline auto PHYSFSX_puts(PHYSFS_File *file, const std::span s) { return PHYSFS_write(file, s.data(), 1, s.size()); } static inline auto PHYSFSX_puts_literal(PHYSFS_File *file, const std::span s) { return PHYSFS_write(file, s.data(), 1, s.size() - 1); } static inline int PHYSFSX_fgetc(PHYSFS_File *const fp) { unsigned char c; if (PHYSFS_read(fp, &c, 1, 1) != 1) return EOF; return c; } static inline int PHYSFSX_fseek(PHYSFS_File *fp, long int offset, int where) { int c, goal_position; switch(where) { case SEEK_SET: goal_position = offset; break; case SEEK_CUR: goal_position = PHYSFS_tell(fp) + offset; break; case SEEK_END: goal_position = PHYSFS_fileLength(fp) + offset; break; default: return 1; } c = PHYSFS_seek(fp, goal_position); return !c; } template struct PHYSFSX_gets_line_t { PHYSFSX_gets_line_t() = default; PHYSFSX_gets_line_t(const PHYSFSX_gets_line_t &) = delete; PHYSFSX_gets_line_t &operator=(const PHYSFSX_gets_line_t &) = delete; PHYSFSX_gets_line_t(PHYSFSX_gets_line_t &&) = default; PHYSFSX_gets_line_t &operator=(PHYSFSX_gets_line_t &&) = default; using line_t = std::array; #if DXX_HAVE_POISON /* Force onto heap to improve checker accuracy */ std::unique_ptr m_line; const line_t &line() const { return *m_line.get(); } line_t &line() { return *m_line.get(); } std::span next() { m_line = std::make_unique(); return *m_line.get(); } #else line_t m_line; const line_t &line() const { return m_line; } line_t &line() { return m_line; } std::span next() { return m_line; } #endif operator line_t &() { return line(); } operator const line_t &() const { return line(); } operator char *() { return line().data(); } operator const char *() const { return line().data(); } typename line_t::reference operator[](typename line_t::size_type i) { return line()[i]; } typename line_t::reference operator[](int i) { return operator[](static_cast(i)); } typename line_t::const_reference operator[](typename line_t::size_type i) const { return line()[i]; } typename line_t::const_reference operator[](int i) const { return operator[](static_cast(i)); } constexpr std::size_t size() const { return N; } typename line_t::const_iterator begin() const { return line().begin(); } typename line_t::const_iterator end() const { return line().end(); } }; template <> struct PHYSFSX_gets_line_t<0> { #define DXX_ALLOCATE_PHYSFS_LINE(n) std::make_unique(n) #if !DXX_HAVE_POISON const #endif std::unique_ptr m_line; const std::size_t m_length; PHYSFSX_gets_line_t(const std::size_t n) : #if !DXX_HAVE_POISON m_line(DXX_ALLOCATE_PHYSFS_LINE(n)), #endif m_length(n) { } char *line() { return m_line.get(); } const char *line() const { return m_line.get(); } std::span next() { #if DXX_HAVE_POISON /* Reallocate to tell checker to undefine the buffer */ m_line = DXX_ALLOCATE_PHYSFS_LINE(m_length); #endif return std::span(m_line.get(), m_length); } std::size_t size() const { return m_length; } operator const char *() const { return m_line.get(); } const char *begin() const { return *this; } const char *end() const { return begin() + m_length; } operator const void *() const = delete; #undef DXX_ALLOCATE_PHYSFS_LINE }; class PHYSFSX_fgets_t { [[nodiscard]] static char *get(std::span buf, PHYSFS_File *const fp); template [[nodiscard]] static char *get(const std::span buf, std::size_t offset, PHYSFS_File *const fp) { if (offset > buf.size()) throw std::invalid_argument("offset too large"); return get(buf.subspan(offset), fp); } public: template [[nodiscard]] __attribute_nonnull() char *operator()(PHYSFSX_gets_line_t &buf, PHYSFS_File *const fp, std::size_t offset = 0) const { return get(buf.next(), offset, fp); } template [[nodiscard]] __attribute_nonnull() char *operator()(ntstring &buf, PHYSFS_File *const fp, std::size_t offset = 0) const { auto r = get(std::span(buf), offset, fp); buf.back() = 0; return r; } }; constexpr PHYSFSX_fgets_t PHYSFSX_fgets{}; int PHYSFSX_printf(PHYSFS_File *file, const char *format) = delete; __attribute_format_printf(2, 3) static inline int PHYSFSX_printf(PHYSFS_File *file, const char *format, ...) { char buffer[1024]; va_list args; va_start(args, format); const std::size_t len = std::max(vsnprintf(buffer, sizeof(buffer), format, args), 0); va_end(args); return PHYSFSX_puts(file, {buffer, len}); } #define PHYSFSX_writeFix PHYSFS_writeSLE32 #define PHYSFSX_writeFixAng PHYSFS_writeSLE16 static inline int PHYSFSX_writeVector(PHYSFS_File *file, const vms_vector &v) { if (PHYSFSX_writeFix(file, v.x) < 1 || PHYSFSX_writeFix(file, v.y) < 1 || PHYSFSX_writeFix(file, v.z) < 1) return 0; return 1; } [[noreturn]] __attribute_cold void PHYSFSX_read_helper_report_error(const char *const filename, const unsigned line, const char *const func, PHYSFS_File *const file); template static T PHYSFSX_read_helper(const char *const filename, const unsigned line, const char *const func, PHYSFS_File *const file) { T i; if (!F(file, &i)) PHYSFSX_read_helper_report_error(filename, line, func, file); return i; } template static void PHYSFSX_read_sequence_helper(const char *const filename, const unsigned line, const char *const func, PHYSFS_File *const file, T2 *const i) { if (unlikely(!F(file, &(i->*m1)) || !F(file, &(i->*m2)) || !F(file, &(i->*m3)))) PHYSFSX_read_helper_report_error(filename, line, func, file); } static inline int PHYSFSX_readS8(PHYSFS_File *const file, int8_t *const b) { return (PHYSFS_read(file, b, sizeof(*b), 1) == 1); } #define PHYSFSX_readByte(F) (PHYSFSX_read_helper(__FILE__, __LINE__, __func__, (F))) #define PHYSFSX_readShort(F) (PHYSFSX_read_helper(__FILE__, __LINE__, __func__, (F))) #define PHYSFSX_readInt(F) (PHYSFSX_read_helper(__FILE__, __LINE__, __func__, (F))) #define PHYSFSX_readFix(F) (PHYSFSX_read_helper(__FILE__, __LINE__, __func__, (F))) #define PHYSFSX_readFixAng(F) (PHYSFSX_read_helper(__FILE__, __LINE__, __func__, (F))) #define PHYSFSX_readVector(F,V) (PHYSFSX_read_sequence_helper(__FILE__, __LINE__, __func__, (F), &(V))) #define PHYSFSX_readAngleVec(V,F) (PHYSFSX_read_sequence_helper(__FILE__, __LINE__, __func__, (F), (V))) static inline void PHYSFSX_readMatrix(const char *const filename, const unsigned line, const char *const func, vms_matrix *const m, PHYSFS_File *const file) { auto &PHYSFSX_readVector = PHYSFSX_read_sequence_helper; (PHYSFSX_readVector)(filename, line, func, file, &m->rvec); (PHYSFSX_readVector)(filename, line, func, file, &m->uvec); (PHYSFSX_readVector)(filename, line, func, file, &m->fvec); } #define PHYSFSX_readMatrix(M,F) ((PHYSFSX_readMatrix)(__FILE__, __LINE__, __func__, (M), (F))) class PHYSFS_File_deleter { public: int operator()(PHYSFS_File *fp) const { return PHYSFS_close(fp); } }; class RAIIPHYSFS_File : public std::unique_ptr { typedef std::unique_ptr base_t; public: using base_t::base_t; using base_t::operator bool; operator PHYSFS_File *() const && = delete; operator PHYSFS_File *() const & { return get(); } int close() { /* Like reset(), but returns result */ int r = get_deleter()(get()); if (r) release(); return r; } template bool operator==(T) const = delete; template bool operator!=(T) const = delete; }; typedef char file_extension_t[5]; [[nodiscard]] __attribute_nonnull() int PHYSFSX_checkMatchingExtension(const char *filename, const std::ranges::subrange range); enum class physfs_search_path : int { prepend, append, }; PHYSFS_ErrorCode PHYSFSX_addRelToSearchPath(const char *relname, std::array &realPath, physfs_search_path); void PHYSFSX_removeRelFromSearchPath(const char *relname); extern int PHYSFSX_fsize(const char *hogname); extern void PHYSFSX_listSearchPathContent(); [[nodiscard]] int PHYSFSX_getRealPath(const char *stdPath, std::array &realPath); class PHYSFS_unowned_storage_mount_deleter { public: void operator()(const char *const p) const noexcept { PHYSFS_unmount(p); } }; class PHYSFS_computed_path_mount_deleter : PHYSFS_unowned_storage_mount_deleter, std::default_delete> { public: using element_type = std::array; PHYSFS_computed_path_mount_deleter() = default; PHYSFS_computed_path_mount_deleter(const PHYSFS_computed_path_mount_deleter &) = default; PHYSFS_computed_path_mount_deleter(PHYSFS_computed_path_mount_deleter &&) = default; PHYSFS_computed_path_mount_deleter(std::default_delete &&d) : std::default_delete(std::move(d)) { } void operator()(element_type *const p) const noexcept { PHYSFS_unowned_storage_mount_deleter::operator()(p->data()); std::default_delete::operator()(p); } }; /* RAIIPHYSFS_LiteralMount takes a pointer to storage, but does not take * ownership of the underlying storage. On destruction, it will pass * that pointer to PHYSFS_unmount. The pointer must remain valid until * RAIIPHYSFS_LiteralMount is destroyed. */ using RAIIPHYSFS_LiteralMount = std::unique_ptr; /* RAIIPHYSFS_ComputedPathMount owns a pointer to allocated storage. On * destruction, it will pass that pointer to PHYSFS_unmount, then free * the pointer. */ using RAIIPHYSFS_ComputedPathMount = std::unique_ptr; RAIIPHYSFS_LiteralMount make_PHYSFSX_LiteralMount(const char *const name, physfs_search_path); RAIIPHYSFS_ComputedPathMount make_PHYSFSX_ComputedPathMount(const char *const name, physfs_search_path position); extern int PHYSFSX_rename(const char *oldpath, const char *newpath); #define PHYSFSX_exists(F,I) ((I) ? PHYSFSX_exists_ignorecase(F) : PHYSFS_exists(F)) int PHYSFSX_exists_ignorecase(const char *filename); std::pair PHYSFSX_openReadBuffered(const char *filename); std::pair PHYSFSX_openWriteBuffered(const char *filename); extern void PHYSFSX_addArchiveContent(); extern void PHYSFSX_removeArchiveContent(); } #ifdef dsx namespace dsx { bool PHYSFSX_init(int argc, char *argv[]); int PHYSFSX_checkSupportedArchiveTypes(); #if defined(DXX_BUILD_DESCENT_II) RAIIPHYSFS_ComputedPathMount make_PHYSFSX_ComputedPathMount(const char *const name1, const char *const name2, physfs_search_path); #endif } #endif #endif