From bdf4bd07a8c49da6ee005830abff957564aec17d Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Mon, 5 Feb 2018 23:34:01 -1000 Subject: [PATCH] Use asynchronous I/O for Card access --- CMakeLists.txt | 5 +- include/kabufuda/AsyncIO.hpp | 47 ++++ include/kabufuda/Card.hpp | 76 +++--- include/kabufuda/Util.hpp | 85 ++----- lib/kabufuda/AsyncIOPosix.cpp | 183 ++++++++++++++ lib/kabufuda/AsyncIOWin32.cpp | 4 + lib/kabufuda/Card.cpp | 437 ++++++++++++++++++++-------------- test/main.cpp | 4 +- 8 files changed, 548 insertions(+), 293 deletions(-) create mode 100644 include/kabufuda/AsyncIO.hpp create mode 100644 lib/kabufuda/AsyncIOPosix.cpp create mode 100644 lib/kabufuda/AsyncIOWin32.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 26508d0..91d26a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,11 +9,14 @@ set(KABUFUDA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE PATH "kabufud unset(PLAT_SRCS) if(WIN32) -list(APPEND PLAT_SRCS lib/kabufuda/winsupport.cpp include/kabufuda/winsupport.hpp) +list(APPEND PLAT_SRCS lib/kabufuda/winsupport.cpp include/kabufuda/winsupport.hpp lib/kabufuda/AsyncIOWin32.cpp) +else() +list(APPEND PLAT_SRCS lib/kabufuda/AsyncIOPosix.cpp) endif() add_library(kabufuda STATIC include/kabufuda/Constants.hpp + include/kabufuda/AsyncIO.hpp include/kabufuda/BlockAllocationTable.hpp lib/kabufuda/BlockAllocationTable.cpp include/kabufuda/Card.hpp lib/kabufuda/Card.cpp include/kabufuda/Directory.hpp lib/kabufuda/Directory.cpp diff --git a/include/kabufuda/AsyncIO.hpp b/include/kabufuda/AsyncIO.hpp new file mode 100644 index 0000000..a31d123 --- /dev/null +++ b/include/kabufuda/AsyncIO.hpp @@ -0,0 +1,47 @@ +#ifndef __KABU_ASYNCIO_HPP__ +#define __KABU_ASYNCIO_HPP__ + +#ifndef _WIN32 +#include +using SizeReturn = ssize_t; +#else +#include +using SizeReturn = SSIZE_T; +#endif + +#include "Util.hpp" +#include + +namespace kabufuda +{ + +class AsyncIO +{ +#ifndef _WIN32 + int m_fd = -1; + std::vector> m_queue; +#else +#endif + size_t m_maxBlock = 0; +public: + AsyncIO() = default; + AsyncIO(SystemStringView filename, bool truncate = false); + ~AsyncIO(); + AsyncIO(AsyncIO&& other); + AsyncIO& operator=(AsyncIO&& other); + AsyncIO(const AsyncIO* other) = delete; + AsyncIO& operator=(const AsyncIO& other) = delete; + void resizeQueue(size_t queueSz) { m_queue.resize(queueSz); } + SizeReturn syncRead(void* buf, size_t length, off_t offset); + bool asyncRead(size_t qIdx, void* buf, size_t length, off_t offset); + SizeReturn syncWrite(const void* buf, size_t length, off_t offset); + bool asyncWrite(size_t qIdx, const void* buf, size_t length, off_t offset); + ECardResult pollStatus(size_t qIdx, SizeReturn* szRet = nullptr) const; + ECardResult pollStatus() const; + void waitForCompletion() const; + operator bool() const { return m_fd != -1; } +}; + +} + +#endif // __KABU_ASYNCIO_HPP__ diff --git a/include/kabufuda/Card.hpp b/include/kabufuda/Card.hpp index 6961508..cb10079 100644 --- a/include/kabufuda/Card.hpp +++ b/include/kabufuda/Card.hpp @@ -5,6 +5,7 @@ #include "Directory.hpp" #include "File.hpp" #include "Util.hpp" +#include "AsyncIO.hpp" #include #include @@ -12,7 +13,6 @@ #define CARD_FILENAME_MAX 32 #define CARD_ICON_MAX 8 -#undef NOFILE namespace kabufuda { @@ -29,24 +29,6 @@ public: operator bool() const { return getFileNo() != -1; } }; -enum class ECardResult -{ - CRC_MISMATCH = -1003, /* Extension enum for Retro's CRC check */ - FATAL_ERROR = -128, - ENCODING = -13, - NAMETOOLONG = -12, - INSSPACE = -9, - NOENT = -8, - EXIST = -7, - BROKEN = -6, - IOERROR = -5, - NOFILE = -4, - NOCARD = -3, - WRONGDEVICE = -2, - BUSY = -1, - READY = 0 -}; - struct ProbeResults { ECardResult x0_error; @@ -102,31 +84,35 @@ struct CardStat class Card { #pragma pack(push, 4) + struct CardHeader + { + uint8_t m_serial[12]; + uint64_t m_formatTime; + int32_t m_sramBias; + uint32_t m_sramLanguage; + uint32_t m_unknown; + uint16_t m_deviceId; /* 0 for Slot A, 1 for Slot B */ + uint16_t m_sizeMb; + uint16_t m_encoding; + uint8_t __padding[468]; + uint16_t m_updateCounter; + uint16_t m_checksum; + uint16_t m_checksumInv; + void _swapEndian(); + }; union { - struct - { - uint8_t m_serial[12]; - uint64_t m_formatTime; - int32_t m_sramBias; - uint32_t m_sramLanguage; - uint32_t m_unknown; - uint16_t m_deviceId; /* 0 for Slot A, 1 for Slot B */ - uint16_t m_sizeMb; - uint16_t m_encoding; - uint8_t __padding[468]; - uint16_t m_updateCounter; - uint16_t m_checksum; - uint16_t m_checksumInv; - }; + CardHeader m_ch; uint8_t __raw[BlockSize]; }; - + CardHeader m_tmpCh; #pragma pack(pop) SystemString m_filename; - FILE* m_fileHandle = nullptr; + AsyncIO m_fileHandle; Directory m_dirs[2]; BlockAllocationTable m_bats[2]; + Directory m_tmpDirs[2]; + BlockAllocationTable m_tmpBats[2]; uint8_t m_currentDir; uint8_t m_currentBat; @@ -134,12 +120,15 @@ class Card char m_game[5] = {'\0'}; char m_maker[3] = {'\0'}; - void _swapEndian(); void _updateDirAndBat(const Directory& dir, const BlockAllocationTable& bat); void _updateChecksum(); File* _fileFromHandle(const FileHandle& fh) const; void _deleteFile(File& f, BlockAllocationTable& bat); + bool m_dirty = false; + bool m_opened = false; + ECardResult _pumpOpen(); + public: Card(); /** @@ -157,7 +146,7 @@ public: * @param game * @param maker */ - Card(SystemStringView filepath, const char* game = nullptr, const char* maker = nullptr); + Card(const char* game = nullptr, const char* maker = nullptr); ~Card(); /** @@ -237,7 +226,7 @@ public: * @param buf * @param size */ - ECardResult write(FileHandle& fh, const void* buf, size_t size); + ECardResult asyncWrite(FileHandle& fh, const void* buf, size_t size); /** * @brief read @@ -245,7 +234,7 @@ public: * @param dst * @param size */ - ECardResult read(FileHandle& fh, void* dst, size_t size); + ECardResult asyncRead(FileHandle& fh, void* dst, size_t size); /** * @brief seek @@ -336,6 +325,7 @@ public: */ ECardResult setStatus(uint32_t fileNo, const CardStat& stat); +#if 0 // TODO: Async-friendly implementations /** * @brief Copies a file from the current Card instance to a specified Card instance * @param fh The file to copy @@ -351,6 +341,7 @@ public: * @return */ bool moveFileTo(FileHandle& fh, Card& dest); +#endif /** * @brief Sets the current game, if not null any openFile requests will only return files that match this game @@ -417,6 +408,11 @@ public: */ void commit(); + /** + * @brief Opens card image (does nothing if currently open path matches) + */ + bool open(SystemStringView filepath); + /** * @brief Commits changes to disk and closes host file */ diff --git a/include/kabufuda/Util.hpp b/include/kabufuda/Util.hpp index a2c47e3..b1ef901 100644 --- a/include/kabufuda/Util.hpp +++ b/include/kabufuda/Util.hpp @@ -250,71 +250,6 @@ typedef struct stat Sstat; uint64_t getGCTime(); -enum class FileLockType -{ - None = 0, - Read, - Write -}; -static inline FILE* Fopen(const SystemChar* path, const SystemChar* mode, FileLockType lock = FileLockType::None) -{ -#if CARD_UCS2 - FILE* fp = _wfopen(path, mode); - if (!fp) - return nullptr; -#else - FILE* fp = fopen(path, mode); - if (!fp) - return nullptr; -#endif - - if (lock != FileLockType::None) - { -#if _WIN32 - OVERLAPPED ov = {}; - LockFileEx((HANDLE)(uintptr_t)_fileno(fp), (lock == FileLockType::Write) ? LOCKFILE_EXCLUSIVE_LOCK : 0, 0, 0, 1, - &ov); -#else - if (flock(fileno(fp), ((lock == FileLockType::Write) ? LOCK_EX : LOCK_SH) | LOCK_NB)) - fprintf(stderr, "flock %s: %s", path, strerror(errno)); -#endif - } - - return fp; -} - -static inline int FSeek(FILE* fp, int64_t offset, int whence) -{ -#if _WIN32 - return _fseeki64(fp, offset, whence); -#elif __APPLE__ || __FreeBSD__ - return fseeko(fp, offset, whence); -#else - return fseeko64(fp, offset, whence); -#endif -} - -static inline int64_t FTell(FILE* fp) -{ -#if _WIN32 - return _ftelli64(fp); -#elif __APPLE__ || __FreeBSD__ - return ftello(fp); -#else - return ftello64(fp); -#endif -} - -static inline int Rename(const SystemChar* oldpath, const SystemChar* newpath) -{ -#if CARD_UCS2 - //return _wrename(oldpath, newpath); - return MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0; -#else - return rename(oldpath, newpath); -#endif -} - #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif @@ -349,6 +284,26 @@ static inline int Stat(const SystemChar* path, Sstat* statOut) * @param checksumInv */ void calculateChecksumBE(const uint16_t* data, size_t len, uint16_t* checksum, uint16_t* checksumInv); + +#undef NOFILE + +enum class ECardResult +{ + CRC_MISMATCH = -1003, /* Extension enum for Retro's CRC check */ + FATAL_ERROR = -128, + ENCODING = -13, + NAMETOOLONG = -12, + INSSPACE = -9, + NOENT = -8, + EXIST = -7, + BROKEN = -6, + IOERROR = -5, + NOFILE = -4, + NOCARD = -3, + WRONGDEVICE = -2, + BUSY = -1, + READY = 0 +}; } #endif // __KABU_UTIL_HPP__ diff --git a/lib/kabufuda/AsyncIOPosix.cpp b/lib/kabufuda/AsyncIOPosix.cpp new file mode 100644 index 0000000..a43f66b --- /dev/null +++ b/lib/kabufuda/AsyncIOPosix.cpp @@ -0,0 +1,183 @@ +#include "kabufuda/AsyncIO.hpp" + +namespace kabufuda +{ + +AsyncIO::AsyncIO(SystemStringView filename, bool truncate) +{ + m_fd = open(filename.data(), O_RDWR | O_CREAT | (truncate ? O_TRUNC : 0)); +} + +AsyncIO::~AsyncIO() +{ + if (*this) + { + aio_cancel(m_fd, nullptr); + close(m_fd); + } +} + +AsyncIO::AsyncIO(AsyncIO&& other) +{ + m_fd = other.m_fd; + other.m_fd = -1; + m_queue = std::move(other.m_queue); + m_maxBlock = other.m_maxBlock; +} + +AsyncIO& AsyncIO::operator=(AsyncIO&& other) +{ + if (*this) + { + aio_cancel(m_fd, nullptr); + close(m_fd); + } + m_fd = other.m_fd; + other.m_fd = -1; + m_queue = std::move(other.m_queue); + m_maxBlock = other.m_maxBlock; + return *this; +} + +SizeReturn AsyncIO::syncRead(void* buf, size_t length, off_t offset) +{ + lseek(m_fd, offset, SEEK_SET); + return read(m_fd, buf, length); +} + +bool AsyncIO::asyncRead(size_t qIdx, void* buf, size_t length, off_t offset) +{ + struct aiocb& aio = m_queue[qIdx].first; + if (aio.aio_fildes) + { +#ifndef NDEBUG + fprintf(stderr, "WARNING: synchronous kabufuda fallback, check access polling\n"); +#endif + const struct aiocb* aiop = &aio; + struct timespec ts = {2, 0}; + while (aio_suspend(&aiop, 1, &ts) && errno == EINTR) {} + if (aio_error(&aio) == 0) + aio_return(&aio); + } + memset(&aio, 0, sizeof(struct aiocb)); + aio.aio_fildes = m_fd; + aio.aio_offset = offset; + aio.aio_buf = buf; + aio.aio_nbytes = length; + m_maxBlock = std::max(m_maxBlock, qIdx + 1); + return aio_read(&aio) == 0; +} + +SizeReturn AsyncIO::syncWrite(const void* buf, size_t length, off_t offset) +{ + lseek(m_fd, offset, SEEK_SET); + return write(m_fd, buf, length); +} + +bool AsyncIO::asyncWrite(size_t qIdx, const void* buf, size_t length, off_t offset) +{ + struct aiocb& aio = m_queue[qIdx].first; + if (aio.aio_fildes) + { +#ifndef NDEBUG + fprintf(stderr, "WARNING: synchronous kabufuda fallback, check access polling\n"); +#endif + const struct aiocb* aiop = &aio; + struct timespec ts = {2, 0}; + while (aio_suspend(&aiop, 1, &ts) && errno == EINTR) {} + if (aio_error(&aio) == 0) + aio_return(&aio); + } + memset(&aio, 0, sizeof(struct aiocb)); + aio.aio_fildes = m_fd; + aio.aio_offset = offset; + aio.aio_buf = const_cast(buf); + aio.aio_nbytes = length; + m_maxBlock = std::max(m_maxBlock, qIdx + 1); + return aio_write(&aio) == 0; +} + +ECardResult AsyncIO::pollStatus(size_t qIdx, SizeReturn* szRet) const +{ + auto& aio = const_cast(this)->m_queue[qIdx]; + if (aio.first.aio_fildes == 0) + { + if (szRet) + *szRet = aio.second; + return ECardResult::READY; + } + switch (aio_error(&aio.first)) + { + case 0: + aio.second = aio_return(&aio.first); + aio.first.aio_fildes = 0; + if (szRet) + *szRet = aio.second; + return ECardResult::READY; + case EINPROGRESS: + return ECardResult::BUSY; + default: + return ECardResult::IOERROR; + } +} + +ECardResult AsyncIO::pollStatus() const +{ + ECardResult result = ECardResult::READY; + for (auto it = const_cast(this)->m_queue.begin(); + it != const_cast(this)->m_queue.begin() + m_maxBlock; + ++it) + { + auto& aio = *it; + if (aio.first.aio_fildes == 0) + continue; + switch (aio_error(&aio.first)) + { + case 0: + aio.second = aio_return(&aio.first); + aio.first.aio_fildes = 0; + break; + case EINPROGRESS: + if (result > ECardResult::BUSY) + result = ECardResult::BUSY; + break; + default: + if (result > ECardResult::IOERROR) + result = ECardResult::IOERROR; + break; + } + } + if (result == ECardResult::READY) + const_cast(this)->m_maxBlock = 0; + return result; +} + +void AsyncIO::waitForCompletion() const +{ + for (auto it = const_cast(this)->m_queue.begin(); + it != const_cast(this)->m_queue.begin() + m_maxBlock; + ++it) + { + auto& aio = *it; + if (aio.first.aio_fildes == 0) + continue; + switch (aio_error(&aio.first)) + { + case 0: + aio.second = aio_return(&aio.first); + aio.first.aio_fildes = 0; + break; + case EINPROGRESS: + { + const struct aiocb* aiop = &aio.first; + struct timespec ts = {2, 0}; + while (aio_suspend(&aiop, 1, &ts) && errno == EINTR) {} + break; + } + default: + break; + } + } +} + +} diff --git a/lib/kabufuda/AsyncIOWin32.cpp b/lib/kabufuda/AsyncIOWin32.cpp new file mode 100644 index 0000000..e63492e --- /dev/null +++ b/lib/kabufuda/AsyncIOWin32.cpp @@ -0,0 +1,4 @@ +// +// Created by Jack Andersen on 2/5/18. +// + diff --git a/lib/kabufuda/Card.cpp b/lib/kabufuda/Card.cpp index e536a7d..60cff76 100644 --- a/lib/kabufuda/Card.cpp +++ b/lib/kabufuda/Card.cpp @@ -15,7 +15,7 @@ static void NullFileAccess() fprintf(stderr, "Attempted to access null file\n"); } -void Card::_swapEndian() +void Card::CardHeader::_swapEndian() { m_formatTime = SBig(m_formatTime); m_sramBias = SBig(m_sramBias); @@ -35,8 +35,7 @@ Card::Card(Card&& other) { memmove(__raw, other.__raw, BlockSize); m_filename = std::move(other.m_filename); - m_fileHandle = other.m_fileHandle; - other.m_fileHandle = nullptr; + m_fileHandle = std::move(other.m_fileHandle); m_dirs[0] = std::move(other.m_dirs[0]); m_dirs[1] = std::move(other.m_dirs[1]); m_bats[0] = std::move(other.m_bats[0]); @@ -55,8 +54,7 @@ Card& Card::operator=(Card&& other) memmove(__raw, other.__raw, BlockSize); m_filename = std::move(other.m_filename); - m_fileHandle = other.m_fileHandle; - other.m_fileHandle = nullptr; + m_fileHandle = std::move(other.m_fileHandle); m_dirs[0] = std::move(other.m_dirs[0]); m_dirs[1] = std::move(other.m_dirs[1]); m_bats[0] = std::move(other.m_bats[0]); @@ -71,24 +69,20 @@ Card& Card::operator=(Card&& other) return *this; } -Card::Card(SystemStringView filename, const char* game, const char* maker) : m_filename(filename) +ECardResult Card::_pumpOpen() { - memset(__raw, 0xFF, BlockSize); - if (game && strlen(game) == 4) - memcpy(m_game, game, 4); - if (maker && strlen(maker) == 2) - memcpy(m_maker, maker, 2); + if (m_opened) + return ECardResult::READY; - m_fileHandle = Fopen(m_filename.c_str(), _S("rb")); - if (m_fileHandle) + if (!m_fileHandle) + return ECardResult::NOCARD; + + ECardResult res = m_fileHandle.pollStatus(); + if (res == ECardResult::READY) { - fread(__raw, 1, BlockSize, m_fileHandle); - m_maxBlock = m_sizeMb * MbitToBlocks; - _swapEndian(); - fread(m_dirs[0].__raw, 1, BlockSize, m_fileHandle); - fread(m_dirs[1].__raw, 1, BlockSize, m_fileHandle); - fread(m_bats[0].__raw, 1, BlockSize, m_fileHandle); - fread(m_bats[1].__raw, 1, BlockSize, m_fileHandle); + m_ch._swapEndian(); + m_maxBlock = m_ch.m_sizeMb * MbitToBlocks; + m_fileHandle.resizeQueue(m_maxBlock); m_dirs[0].swapEndian(); m_dirs[1].swapEndian(); @@ -115,11 +109,19 @@ Card::Card(SystemStringView filename, const char* game, const char* maker) : m_f else m_currentBat = 1; - /* Close and reopen in read/write mode */ - fclose(m_fileHandle); - m_fileHandle = Fopen(m_filename.c_str(), _S("r+b")); - rewind(m_fileHandle); + m_opened = true; } + + return res; +} + +Card::Card(const char* game, const char* maker) +{ + memset(__raw, 0xFF, BlockSize); + if (game && strlen(game) == 4) + memcpy(m_game, game, 4); + if (maker && strlen(maker) == 2) + memcpy(m_maker, maker, 2); } Card::~Card() @@ -129,6 +131,10 @@ Card::~Card() ECardResult Card::openFile(const char* filename, FileHandle& handleOut) { + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + handleOut = {}; File* f = m_dirs[m_currentDir].getFile(m_game, m_maker, filename); if (!f || f->m_game[0] == 0xFF) @@ -144,6 +150,10 @@ ECardResult Card::openFile(const char* filename, FileHandle& handleOut) ECardResult Card::openFile(uint32_t fileno, FileHandle& handleOut) { + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + handleOut = {}; File* f = m_dirs[m_currentDir].getFile(fileno); if (!f || f->m_game[0] == 0xFF) @@ -165,13 +175,15 @@ void Card::_updateDirAndBat(const Directory& dir, const BlockAllocationTable& ba updateBat = bat; updateBat.m_updateCounter++; updateBat.updateChecksum(); + + m_dirty = true; } void Card::_updateChecksum() { - _swapEndian(); - calculateChecksumBE(reinterpret_cast(__raw), 0xFE, &m_checksum, &m_checksumInv); - _swapEndian(); + m_ch._swapEndian(); + calculateChecksumBE(reinterpret_cast(__raw), 0xFE, &m_ch.m_checksum, &m_ch.m_checksumInv); + m_ch._swapEndian(); } File* Card::_fileFromHandle(const FileHandle& fh) const @@ -187,6 +199,10 @@ File* Card::_fileFromHandle(const FileHandle& fh) const ECardResult Card::createFile(const char* filename, size_t size, FileHandle& handleOut) { + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + handleOut = {}; if (size <= 0) @@ -283,6 +299,10 @@ void Card::deleteFile(const FileHandle& fh) ECardResult Card::deleteFile(const char* filename) { + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + Directory dir = m_dirs[m_currentDir]; File* f = dir.getFile(m_game, m_maker, filename); if (!f) @@ -296,6 +316,10 @@ ECardResult Card::deleteFile(const char* filename) ECardResult Card::deleteFile(uint32_t fileno) { + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + Directory dir = m_dirs[m_currentDir]; File* f = dir.getFile(fileno); if (!f) @@ -309,6 +333,10 @@ ECardResult Card::deleteFile(uint32_t fileno) ECardResult Card::renameFile(const char* oldName, const char* newName) { + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + if (strlen(newName) > 32) return ECardResult::NAMETOOLONG; @@ -332,107 +360,107 @@ ECardResult Card::renameFile(const char* oldName, const char* newName) return ECardResult::READY; } -ECardResult Card::write(FileHandle& fh, const void* buf, size_t size) +ECardResult Card::asyncWrite(FileHandle& fh, const void* buf, size_t size) { - if (m_fileHandle) + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + + if (!fh) { - if (!fh) - { - NullFileAccess(); - return ECardResult::NOFILE; - } - File* file = m_dirs[m_currentDir].getFile(fh.idx); - if (!file) - return ECardResult::NOFILE; - - /* Block handling is a little different from cache handling, - * since each block can be in an arbitrary location we must - * first find our starting block. - */ - const uint16_t blockId = uint16_t(fh.offset / BlockSize); - uint16_t block = file->m_firstBlock; - for (uint16_t i = 0; i < blockId; i++) - block = m_bats[m_currentBat].getNextBlock(block); - - const uint8_t* tmpBuf = reinterpret_cast(buf); - uint16_t curBlock = block; - uint32_t blockOffset = fh.offset % BlockSize; - size_t rem = size; - while (rem) - { - if (curBlock == 0xFFFF) - return ECardResult::NOFILE; - - size_t cacheSize = rem; - if (cacheSize + blockOffset > BlockSize) - cacheSize = BlockSize - blockOffset; - uint32_t offset = (curBlock * BlockSize) + blockOffset; - fseek(m_fileHandle, offset, SEEK_SET); - if (fwrite(tmpBuf, 1, cacheSize, m_fileHandle) != cacheSize) - return ECardResult::IOERROR; - tmpBuf += cacheSize; - rem -= cacheSize; - blockOffset += cacheSize; - if (blockOffset >= BlockSize) - { - curBlock = m_bats[m_currentBat].getNextBlock(curBlock); - blockOffset = 0; - } - } - fh.offset += size; + NullFileAccess(); + return ECardResult::NOFILE; } + File* file = m_dirs[m_currentDir].getFile(fh.idx); + if (!file) + return ECardResult::NOFILE; + + /* Block handling is a little different from cache handling, + * since each block can be in an arbitrary location we must + * first find our starting block. + */ + const uint16_t blockId = uint16_t(fh.offset / BlockSize); + uint16_t block = file->m_firstBlock; + for (uint16_t i = 0; i < blockId; i++) + block = m_bats[m_currentBat].getNextBlock(block); + + const uint8_t* tmpBuf = reinterpret_cast(buf); + uint16_t curBlock = block; + uint32_t blockOffset = fh.offset % BlockSize; + size_t rem = size; + while (rem) + { + if (curBlock == 0xFFFF) + return ECardResult::NOFILE; + + size_t cacheSize = rem; + if (cacheSize + blockOffset > BlockSize) + cacheSize = BlockSize - blockOffset; + uint32_t offset = (curBlock * BlockSize) + blockOffset; + if (!m_fileHandle.asyncWrite(curBlock, tmpBuf, cacheSize, offset)) + return ECardResult::FATAL_ERROR; + tmpBuf += cacheSize; + rem -= cacheSize; + blockOffset += cacheSize; + if (blockOffset >= BlockSize) + { + curBlock = m_bats[m_currentBat].getNextBlock(curBlock); + blockOffset = 0; + } + } + fh.offset += size; return ECardResult::READY; } -ECardResult Card::read(FileHandle& fh, void* dst, size_t size) +ECardResult Card::asyncRead(FileHandle& fh, void* dst, size_t size) { - if (m_fileHandle) + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + + if (!fh) { - if (!fh) - { - NullFileAccess(); - return ECardResult::NOFILE; - } - File* file = m_dirs[m_currentDir].getFile(fh.idx); - if (!file) - return ECardResult::NOFILE; - /* Block handling is a little different from cache handling, - * since each block can be in an arbitrary location we must - * first find our starting block. - */ - const uint16_t blockId = uint16_t(fh.offset / BlockSize); - uint16_t block = file->m_firstBlock; - for (uint16_t i = 0; i < blockId; i++) - block = m_bats[m_currentBat].getNextBlock(block); - - uint8_t* tmpBuf = reinterpret_cast(dst); - uint16_t curBlock = block; - uint32_t blockOffset = fh.offset % BlockSize; - size_t rem = size; - while (rem) - { - if (curBlock == 0xFFFF) - return ECardResult::NOFILE; - - size_t cacheSize = rem; - if (cacheSize + blockOffset > BlockSize) - cacheSize = BlockSize - blockOffset; - uint32_t offset = (curBlock * BlockSize) + blockOffset; - fseek(m_fileHandle, offset, SEEK_SET); - if (fread(tmpBuf, 1, cacheSize, m_fileHandle) != cacheSize) - return ECardResult::IOERROR; - tmpBuf += cacheSize; - rem -= cacheSize; - blockOffset += cacheSize; - if (blockOffset >= BlockSize) - { - curBlock = m_bats[m_currentBat].getNextBlock(curBlock); - blockOffset = 0; - } - } - fh.offset += size; + NullFileAccess(); + return ECardResult::NOFILE; } + File* file = m_dirs[m_currentDir].getFile(fh.idx); + if (!file) + return ECardResult::NOFILE; + /* Block handling is a little different from cache handling, + * since each block can be in an arbitrary location we must + * first find our starting block. + */ + const uint16_t blockId = uint16_t(fh.offset / BlockSize); + uint16_t block = file->m_firstBlock; + for (uint16_t i = 0; i < blockId; i++) + block = m_bats[m_currentBat].getNextBlock(block); + + uint8_t* tmpBuf = reinterpret_cast(dst); + uint16_t curBlock = block; + uint32_t blockOffset = fh.offset % BlockSize; + size_t rem = size; + while (rem) + { + if (curBlock == 0xFFFF) + return ECardResult::NOFILE; + + size_t cacheSize = rem; + if (cacheSize + blockOffset > BlockSize) + cacheSize = BlockSize - blockOffset; + uint32_t offset = (curBlock * BlockSize) + blockOffset; + if (!m_fileHandle.asyncRead(curBlock, tmpBuf, cacheSize, offset)) + return ECardResult::FATAL_ERROR; + tmpBuf += cacheSize; + rem -= cacheSize; + blockOffset += cacheSize; + if (blockOffset >= BlockSize) + { + curBlock = m_bats[m_currentBat].getNextBlock(curBlock); + blockOffset = 0; + } + } + fh.offset += size; return ECardResult::READY; } @@ -583,6 +611,10 @@ ECardResult Card::getStatus(const FileHandle& fh, CardStat& statOut) const ECardResult Card::getStatus(uint32_t fileNo, CardStat& statOut) const { + ECardResult openRes = const_cast(this)->_pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + const File* file = const_cast(m_dirs[m_currentDir]).getFile(fileNo); if (!file || file->m_game[0] == 0xFF) return ECardResult::NOFILE; @@ -649,6 +681,10 @@ ECardResult Card::setStatus(const FileHandle& fh, const CardStat& stat) ECardResult Card::setStatus(uint32_t fileNo, const CardStat& stat) { + ECardResult openRes = _pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + Directory dir = m_dirs[m_currentDir]; File* file = dir.getFile(fileNo); if (!file || file->m_game[0] == 0xFF) @@ -664,6 +700,7 @@ ECardResult Card::setStatus(uint32_t fileNo, const CardStat& stat) return ECardResult::READY; } +#if 0 // TODO: Async-friendly implementations bool Card::copyFileTo(FileHandle& fh, Card& dest) { if (!canCopy(fh)) @@ -722,6 +759,7 @@ bool Card::moveFileTo(FileHandle& fh, Card& dest) return false; } +#endif void Card::setCurrentGame(const char* game) { @@ -768,19 +806,19 @@ const uint8_t* Card::getCurrentMaker() const void Card::getSerial(uint64_t& serial) { - _swapEndian(); + m_ch._swapEndian(); uint32_t serialBuf[8]; for (uint32_t i = 0; i < 8; i++) serialBuf[i] = SBig(*reinterpret_cast(__raw + (i * 4))); serial = uint64_t(serialBuf[0] ^ serialBuf[2] ^ serialBuf[4] ^ serialBuf[6]) << 32 | (serialBuf[1] ^ serialBuf[3] ^ serialBuf[5] ^ serialBuf[7]); - _swapEndian(); + m_ch._swapEndian(); } void Card::getChecksum(uint16_t& checksum, uint16_t& inverse) { - checksum = m_checksum; - inverse = m_checksumInv; + checksum = m_ch.m_checksum; + inverse = m_ch.m_checksumInv; } void Card::getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) @@ -789,26 +827,28 @@ void Card::getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) filesNotUsed = m_dirs[m_currentDir].numFreeFiles(); } +static std::unique_ptr DummyBlock; + void Card::format(ECardSlot id, ECardSize size, EEncoding encoding) { memset(__raw, 0xFF, BlockSize); uint64_t rand = uint64_t(getGCTime()); - m_formatTime = rand; + m_ch.m_formatTime = rand; for (int i = 0; i < 12; i++) { rand = (((rand * uint64_t(0x41c64e6d)) + uint64_t(0x3039)) >> 16); - m_serial[i] = uint8_t(g_SRAM.flash_id[uint32_t(id)][i] + uint32_t(rand)); + m_ch.m_serial[i] = uint8_t(g_SRAM.flash_id[uint32_t(id)][i] + uint32_t(rand)); rand = (((rand * uint64_t(0x41c64e6d)) + uint64_t(0x3039)) >> 16); rand &= uint64_t(0x7fffULL); } - m_sramBias = int32_t(SBig(g_SRAM.counter_bias)); - m_sramLanguage = uint32_t(SBig(g_SRAM.lang)); - m_unknown = 0; /* 1 works for slot A, 0 both */ - m_deviceId = 0; - m_sizeMb = uint16_t(size); - m_maxBlock = m_sizeMb * MbitToBlocks; - m_encoding = uint16_t(encoding); + m_ch.m_sramBias = int32_t(SBig(g_SRAM.counter_bias)); + m_ch.m_sramLanguage = uint32_t(SBig(g_SRAM.lang)); + m_ch.m_unknown = 0; /* 1 works for slot A, 0 both */ + m_ch.m_deviceId = 0; + m_ch.m_sizeMb = uint16_t(size); + m_maxBlock = m_ch.m_sizeMb * MbitToBlocks; + m_ch.m_encoding = uint16_t(encoding); _updateChecksum(); m_dirs[0] = Directory(); m_dirs[1] = m_dirs[0]; @@ -817,34 +857,35 @@ void Card::format(ECardSlot id, ECardSize size, EEncoding encoding) m_currentDir = 1; m_currentBat = 1; - if (m_fileHandle) - fclose(m_fileHandle); - - m_fileHandle = Fopen(m_filename.c_str(), _S("wb")); + m_fileHandle = {}; + m_fileHandle = AsyncIO(m_filename.c_str(), true); if (m_fileHandle) { - _swapEndian(); - fwrite(__raw, 1, BlockSize, m_fileHandle); - _swapEndian(); - Directory tmpDir = m_dirs[0]; - tmpDir.swapEndian(); - fwrite(tmpDir.__raw, 1, BlockSize, m_fileHandle); - tmpDir = m_dirs[1]; - tmpDir.swapEndian(); - fwrite(tmpDir.__raw, 1, BlockSize, m_fileHandle); - BlockAllocationTable tmpBat = m_bats[0]; - tmpBat.swapEndian(); - fwrite(tmpBat.__raw, 1, BlockSize, m_fileHandle); - tmpBat = m_bats[1]; - tmpBat.swapEndian(); - fwrite(tmpBat.__raw, 1, BlockSize, m_fileHandle); - uint32_t dataLen = ((uint32_t(size) * MbitToBlocks) - 5) * BlockSize; - std::unique_ptr data(new uint8_t[dataLen]); - memset(data.get(), 0xFF, dataLen); - fwrite(data.get(), 1, dataLen, m_fileHandle); - fclose(m_fileHandle); - m_fileHandle = Fopen(m_filename.c_str(), _S("r+b")); + m_tmpCh = m_ch; + m_tmpCh._swapEndian(); + m_fileHandle.asyncWrite(0, &m_tmpCh, BlockSize, 0); + m_tmpDirs[0] = m_dirs[0]; + m_tmpDirs[0].swapEndian(); + m_fileHandle.asyncWrite(1, m_tmpDirs[0].__raw, BlockSize, BlockSize * 1); + m_tmpDirs[1] = m_dirs[1]; + m_tmpDirs[1].swapEndian(); + m_fileHandle.asyncWrite(2, m_tmpDirs[1].__raw, BlockSize, BlockSize * 2); + m_tmpBats[0] = m_bats[0]; + m_tmpBats[0].swapEndian(); + m_fileHandle.asyncWrite(3, m_tmpBats[0].__raw, BlockSize, BlockSize * 3); + m_tmpBats[1] = m_bats[1]; + m_tmpBats[1].swapEndian(); + m_fileHandle.asyncWrite(4, m_tmpBats[1].__raw, BlockSize, BlockSize * 4); + if (!DummyBlock) + { + DummyBlock.reset(new uint8_t[BlockSize]); + memset(DummyBlock.get(), 0xFF, BlockSize); + } + uint32_t blockCount = (uint32_t(size) * MbitToBlocks) - 5; + for (uint32_t i=0 ; i(this)->_pumpOpen(); + if (openRes != ECardResult::READY) + return openRes; + uint16_t ckSum, ckSumInv; - const_cast(*this)._swapEndian(); + const_cast(*this).m_ch._swapEndian(); calculateChecksumBE(reinterpret_cast(__raw), 0xFE, &ckSum, &ckSumInv); - bool res = (ckSum == m_checksum && ckSumInv == m_checksumInv); - const_cast(*this)._swapEndian(); + bool res = (ckSum == m_ch.m_checksum && ckSumInv == m_ch.m_checksumInv); + const_cast(*this).m_ch._swapEndian(); if (!res) return ECardResult::BROKEN; diff --git a/test/main.cpp b/test/main.cpp index 6c255ca..a33d5d9 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -23,9 +23,9 @@ int main() mc.setCanMove(f, true); kabufuda::CardStat stat = {}; mc.setStatus(f, stat); - mc.write(f, "Test\0", strlen("Test") + 1); + mc.asyncWrite(f, "Test\0", strlen("Test") + 1); mc.seek(f, 32, kabufuda::SeekOrigin::Begin); - mc.write(f, "Test\0", strlen("Test") + 1); + mc.asyncWrite(f, "Test\0", strlen("Test") + 1); } return 0; }