// Copyright (c) 2018-2019 HALX99. // Copyright (c) 2020 C4games Ltd #include "platform/FileStream.h" #if AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID # include "platform/android/FileUtils-android.h" # include "base/ZipUtils.h" #endif #include #include #if defined(_WIN32) # include "ntcvt/ntcvt.hpp" #else # include # include # include # define O_READ_FLAGS O_RDONLY, 0 # define O_WRITE_FLAGS O_CREAT | O_RDWR | O_TRUNC, S_IRWXU # define O_APPEND_FLAGS O_APPEND | O_CREAT | O_RDWR, S_IRWXU # define O_OVERLAP_FLAGS O_CREAT | O_RDWR, S_IRWXU # define posix_open_cxx(path, ...) ::open(path.data(), ##__VA_ARGS__) # define posix_open ::open # define posix_close ::close # define posix_lseek ::lseek # define posix_read ::read # define posix_write ::write # define posix_fd2fh(fd) (fd) # define posix_ftruncate ::ftruncate # if defined(__APPLE__) || defined(__EMSCRIPTEN__) # define posix_lseek64 ::lseek # define posix_ftruncate64 ::ftruncate # else # define posix_lseek64 ::lseek64 # define posix_ftruncate64 ::ftruncate64 # endif #endif NS_AX_BEGIN struct PXIoF { int (*read)(const PXFileHandle& fh, void*, unsigned int); int (*write)(const PXFileHandle& fh, const void*, unsigned int); int64_t (*seek)(const PXFileHandle& fh, int64_t, int); int (*close)(PXFileHandle& fh); int64_t (*size)(const PXFileHandle& fh); int (*resize)(const PXFileHandle& fh, int64_t); }; static int64_t axrt_lowio_seek(const PXFileHandle& fh, int64_t offst, int origin); struct __axrt_seek_guard { __axrt_seek_guard(const PXFileHandle& fh) : place(axrt_lowio_seek(fh, 0, SEEK_CUR)), end(axrt_lowio_seek(fh, 0, SEEK_END)), fhh(fh) {} ~__axrt_seek_guard() { axrt_lowio_seek(fhh, place, SEEK_SET); } __axrt_seek_guard(__axrt_seek_guard const&) = delete; __axrt_seek_guard& operator=(__axrt_seek_guard const&) = delete; const PXFileHandle& fhh; int64_t place; int64_t end; }; static inline int __axrt_errno_or(int ec) { auto value = errno; if (!value) errno = value = ec; return value; } #if defined(_WIN32) # define __axrt_truncate_file(fh, size) ::SetEndOfFile(fh) # if !defined(WINAPI_FAMILY) || WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP # define __axrt_create_file(path, omode, smode, cflags, fflags, fattrs) \ CreateFileW(path, omode, smode, nullptr, cflags, fflags | fattrs, nullptr) # else static HANDLE __axrt_create_file(LPCWSTR path, DWORD omode, DWORD smode, DWORD cflags, DWORD fflags, DWORD fattrs) { if (!(fflags | fattrs)) return CreateFile2(path, omode, smode, cflags, nullptr); else { CREATEFILE2_EXTENDED_PARAMETERS params = {0}; params.dwSize = sizeof(params); params.dwFileFlags = fflags; params.dwFileAttributes = fattrs; return CreateFile2(path, omode, smode, cflags, ¶ms); } } # endif # define _LOWIO_SH_DENYNO (FILE_SHARE_READ | FILE_SHARE_WRITE) static bool axrt_lowio_open(std::string_view path, IFileStream::Mode mode, PXFileHandle& fh) { static constexpr DWORD __axrt_open_mode2options[][5] = { // readonly {GENERIC_READ, _LOWIO_SH_DENYNO, OPEN_EXISTING, 0, 0}, // write {GENERIC_READ | GENERIC_WRITE, _LOWIO_SH_DENYNO, CREATE_ALWAYS, 0, FILE_ATTRIBUTE_NORMAL}, // append {GENERIC_READ | GENERIC_WRITE, _LOWIO_SH_DENYNO, OPEN_ALWAYS, 0, FILE_ATTRIBUTE_NORMAL}, // overlap {GENERIC_READ | GENERIC_WRITE, _LOWIO_SH_DENYNO, OPEN_ALWAYS, 0, FILE_ATTRIBUTE_NORMAL}, }; auto wFileName = ntcvt::from_chars(path); const auto opts = __axrt_open_mode2options[(int)mode & 0b11]; fh.osfh = __axrt_create_file(wFileName.c_str(), opts[0], opts[1], opts[2], opts[3], opts[4]); if (fh.osfh != INVALID_HANDLE_VALUE) { fh.append = mode == IFileStream::Mode::APPEND; return true; } return false; } static int axrt_lowio_read(const PXFileHandle& fh, void* buf, unsigned int size) { DWORD dwBytesRead = 0; if (::ReadFile(fh.osfh, buf, size, &dwBytesRead, nullptr)) return static_cast(dwBytesRead); errno = EACCES; return -1; } static int axrt_lowio_write(const PXFileHandle& fh, const void* buf, unsigned int size) { if (fh.append) ::SetFilePointer(fh.osfh, 0, 0, FILE_END); DWORD dwBytesWrite = 0; if (::WriteFile(fh.osfh, buf, size, &dwBytesWrite, nullptr)) return static_cast(dwBytesWrite); errno = EACCES; return -1; } static int64_t axrt_lowio_seek(const PXFileHandle& fh, int64_t offset, int origin) { LARGE_INTEGER seekpos, newpos; seekpos.QuadPart = offset; if (SetFilePointerEx(fh.osfh, seekpos, &newpos, origin)) return newpos.QuadPart; errno = EINVAL; return -1; } static int64_t axrt_lowio_size(const PXFileHandle& fh) { LARGE_INTEGER endpos; return ::GetFileSizeEx(fh.osfh, &endpos) ? endpos.QuadPart : 0; } static int axrt_lowio_close(PXFileHandle& fh) { if (fh.osfh != INVALID_HANDLE_VALUE) { CloseHandle(fh.osfh); fh.osfh = INVALID_HANDLE_VALUE; } return 0; } #else # define __axrt_truncate_file(fh, size) (posix_ftruncate64(fh, size) == 0) static bool axrt_lowio_open(std::string_view path, IFileStream::Mode mode, PXFileHandle& fh) { static constexpr int __axrt_open_mode2options[][3] = { {O_READ_FLAGS}, {O_WRITE_FLAGS}, {O_APPEND_FLAGS}, {O_OVERLAP_FLAGS}, }; const auto opts = __axrt_open_mode2options[(int)mode & 0b11]; fh.osfh = posix_open_cxx(path, opts[0], opts[1]); return fh.osfh != -1; } static int axrt_lowio_read(const PXFileHandle& fh, void* buf, unsigned int size) { return static_cast(posix_read(fh.osfh, buf, size)); } static int axrt_lowio_write(const PXFileHandle& fh, const void* buf, unsigned int size) { return static_cast(posix_write(fh.osfh, buf, size)); } static int64_t axrt_lowio_seek(const PXFileHandle& fh, int64_t offset, int origin) { return posix_lseek64(fh.osfh, offset, origin); } static int64_t axrt_lowio_size(const PXFileHandle& fh) { // Get current file position and seek to end __axrt_seek_guard seek_guard(fh); return seek_guard.end != -1 ? seek_guard.end : 0; } static int axrt_lowio_close(PXFileHandle& fh) { int fd = fh.osfh; if (fd != -1) { fh.osfh = -1; return posix_close(fd); } return 0; } #endif static int axrt_lowio_resize(const PXFileHandle& fh, int64_t size) { // Get current file position and seek to end __axrt_seek_guard seek_guard(fh); if (seek_guard.place == -1 || seek_guard.end == -1) { // EINVAL otherwise (ex: too large of a offset) return __axrt_errno_or(EINVAL); } // move pointer to new sizes int64_t const new_end = axrt_lowio_seek(fh, size, SEEK_SET); if (new_end == -1) return errno; if (!__axrt_truncate_file(fh.osfh, size)) return __axrt_errno_or(EACCES); // Shorten the file by truncating it, ensure restore place don't grater than new file size int64_t const extend = size - seek_guard.end; if (extend < 0 && seek_guard.place > new_end) seek_guard.place = new_end; return 0; } static const PXIoF axrt_lowio_iof = {axrt_lowio_read, axrt_lowio_write, axrt_lowio_seek, axrt_lowio_close, axrt_lowio_size, axrt_lowio_resize}; #if AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID static int axrt_dummy_write(const PXFileHandle& /*fh*/, const void* /*buf*/, unsigned int /*size*/) { return -1; } static int axrt_dummy_resize(const PXFileHandle& /*fh*/, int64_t /*newsize*/) { return -1; } // android AssetManager wrappers static int axrt_asset_read(const PXFileHandle& fh, void* buf, unsigned int size) { return AAsset_read(fh.aasset, buf, size); } static int64_t axrt_asset_seek(const PXFileHandle& fh, int64_t offset, int origin) { return AAsset_seek(fh.aasset, offset, origin); } static int64_t axrt_asset_size(const PXFileHandle& fh) { return AAsset_getLength64(fh.aasset); } static int axrt_asset_close(PXFileHandle& fh) { if (fh.aasset != nullptr) { AAsset_close(fh.aasset); fh.aasset = nullptr; } return 0; } static const PXIoF axrt_asset_iof = {axrt_asset_read, axrt_dummy_write, axrt_asset_seek, axrt_asset_close, axrt_asset_size, axrt_dummy_resize}; // android obb static int axrt_obb_read(const PXFileHandle& fh, void* buf, unsigned int size) { return FileUtilsAndroid::getObbFile()->vread(fh.zentry, buf, size); } static int64_t axrt_obb_seek(const PXFileHandle& fh, int64_t offset, int origin) { return FileUtilsAndroid::getObbFile()->vseek(fh.zentry, offset, origin); } static int64_t axrt_obb_size(const PXFileHandle& fh) { return FileUtilsAndroid::getObbFile()->vsize(fh.zentry); } static int axrt_obb_close(PXFileHandle& fh) { FileUtilsAndroid::getObbFile()->vclose(fh.zentry); return 0; } static const PXIoF axrt_obb_iof = {axrt_obb_read, axrt_dummy_write, axrt_obb_seek, axrt_obb_close, axrt_obb_size, axrt_dummy_resize}; #endif FileStream::~FileStream() { internalClose(); } bool FileStream::open(std::string_view path, IFileStream::Mode mode) { #if AX_TARGET_PLATFORM != AX_PLATFORM_ANDROID if (axrt_lowio_open(path, mode, _handle)) this->_iof = &axrt_lowio_iof; #else // Android if (path[0] != '/') { // from package, always readonly size_t position = path.find("assets/"); // "assets/" is at the beginning of the path and we don't want it const auto relativePath = 0 == position ? path.substr(sizeof("assets/") - 1) : path; auto obb = FileUtilsAndroid::getObbFile(); if (obb && (_handle.zentry = obb->vopen(relativePath))) { this->_iof = &axrt_obb_iof; } else { AAssetManager* asMgr = FileUtilsAndroid::getAssetManager(); _handle.aasset = AAssetManager_open(asMgr, relativePath.data(), AASSET_MODE_UNKNOWN); if (_handle.aasset) this->_iof = &axrt_asset_iof; } } else { // otherwise, as a absolutely path if (axrt_lowio_open(path, mode, _handle)) this->_iof = &axrt_lowio_iof; } #endif return !!this->_iof; } osfhnd_t FileStream::nativeHandle() const { return _iof == &axrt_lowio_iof ? _handle.osfh : (osfhnd_t)-1; } int FileStream::internalClose() { if (_iof) { int ret = _iof->close(_handle); _iof = nullptr; return ret; } return 0; } int FileStream::close() { return internalClose(); } int64_t FileStream::seek(int64_t offset, int origin) const { assert(_iof); return _iof->seek(_handle, offset, origin); } int FileStream::read(void* buf, unsigned int size) const { assert(_iof); return _iof->read(_handle, buf, size); } int FileStream::write(const void* buf, unsigned int size) const { assert(_iof); return _iof->write(_handle, buf, size); } int64_t FileStream::size() const { assert(_iof); return _iof->size(_handle); } int64_t FileStream::tell() const { assert(_iof); return _iof->seek(_handle, 0, SEEK_CUR); } bool FileStream::resize(int64_t size) const { assert(_iof); return _iof->resize(_handle, size) == 0; } bool FileStream::isOpen() const { return !!_iof; } NS_AX_END