Use asynchronous I/O for Card access

This commit is contained in:
Jack Andersen 2018-02-05 23:34:01 -10:00
parent 8052a6372e
commit bdf4bd07a8
8 changed files with 548 additions and 293 deletions

View File

@ -9,11 +9,14 @@ set(KABUFUDA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE PATH "kabufud
unset(PLAT_SRCS) unset(PLAT_SRCS)
if(WIN32) 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() endif()
add_library(kabufuda STATIC add_library(kabufuda STATIC
include/kabufuda/Constants.hpp include/kabufuda/Constants.hpp
include/kabufuda/AsyncIO.hpp
include/kabufuda/BlockAllocationTable.hpp lib/kabufuda/BlockAllocationTable.cpp include/kabufuda/BlockAllocationTable.hpp lib/kabufuda/BlockAllocationTable.cpp
include/kabufuda/Card.hpp lib/kabufuda/Card.cpp include/kabufuda/Card.hpp lib/kabufuda/Card.cpp
include/kabufuda/Directory.hpp lib/kabufuda/Directory.cpp include/kabufuda/Directory.hpp lib/kabufuda/Directory.cpp

View File

@ -0,0 +1,47 @@
#ifndef __KABU_ASYNCIO_HPP__
#define __KABU_ASYNCIO_HPP__
#ifndef _WIN32
#include <aio.h>
using SizeReturn = ssize_t;
#else
#include <windows.h>
using SizeReturn = SSIZE_T;
#endif
#include "Util.hpp"
#include <vector>
namespace kabufuda
{
class AsyncIO
{
#ifndef _WIN32
int m_fd = -1;
std::vector<std::pair<struct aiocb, SizeReturn>> 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__

View File

@ -5,6 +5,7 @@
#include "Directory.hpp" #include "Directory.hpp"
#include "File.hpp" #include "File.hpp"
#include "Util.hpp" #include "Util.hpp"
#include "AsyncIO.hpp"
#include <string> #include <string>
#include <vector> #include <vector>
@ -12,7 +13,6 @@
#define CARD_FILENAME_MAX 32 #define CARD_FILENAME_MAX 32
#define CARD_ICON_MAX 8 #define CARD_ICON_MAX 8
#undef NOFILE
namespace kabufuda namespace kabufuda
{ {
@ -29,24 +29,6 @@ public:
operator bool() const { return getFileNo() != -1; } 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 struct ProbeResults
{ {
ECardResult x0_error; ECardResult x0_error;
@ -102,8 +84,7 @@ struct CardStat
class Card class Card
{ {
#pragma pack(push, 4) #pragma pack(push, 4)
union { struct CardHeader
struct
{ {
uint8_t m_serial[12]; uint8_t m_serial[12];
uint64_t m_formatTime; uint64_t m_formatTime;
@ -117,16 +98,21 @@ class Card
uint16_t m_updateCounter; uint16_t m_updateCounter;
uint16_t m_checksum; uint16_t m_checksum;
uint16_t m_checksumInv; uint16_t m_checksumInv;
void _swapEndian();
}; };
union {
CardHeader m_ch;
uint8_t __raw[BlockSize]; uint8_t __raw[BlockSize];
}; };
CardHeader m_tmpCh;
#pragma pack(pop) #pragma pack(pop)
SystemString m_filename; SystemString m_filename;
FILE* m_fileHandle = nullptr; AsyncIO m_fileHandle;
Directory m_dirs[2]; Directory m_dirs[2];
BlockAllocationTable m_bats[2]; BlockAllocationTable m_bats[2];
Directory m_tmpDirs[2];
BlockAllocationTable m_tmpBats[2];
uint8_t m_currentDir; uint8_t m_currentDir;
uint8_t m_currentBat; uint8_t m_currentBat;
@ -134,12 +120,15 @@ class Card
char m_game[5] = {'\0'}; char m_game[5] = {'\0'};
char m_maker[3] = {'\0'}; char m_maker[3] = {'\0'};
void _swapEndian();
void _updateDirAndBat(const Directory& dir, const BlockAllocationTable& bat); void _updateDirAndBat(const Directory& dir, const BlockAllocationTable& bat);
void _updateChecksum(); void _updateChecksum();
File* _fileFromHandle(const FileHandle& fh) const; File* _fileFromHandle(const FileHandle& fh) const;
void _deleteFile(File& f, BlockAllocationTable& bat); void _deleteFile(File& f, BlockAllocationTable& bat);
bool m_dirty = false;
bool m_opened = false;
ECardResult _pumpOpen();
public: public:
Card(); Card();
/** /**
@ -157,7 +146,7 @@ public:
* @param game * @param game
* @param maker * @param maker
*/ */
Card(SystemStringView filepath, const char* game = nullptr, const char* maker = nullptr); Card(const char* game = nullptr, const char* maker = nullptr);
~Card(); ~Card();
/** /**
@ -237,7 +226,7 @@ public:
* @param buf * @param buf
* @param size * @param size
*/ */
ECardResult write(FileHandle& fh, const void* buf, size_t size); ECardResult asyncWrite(FileHandle& fh, const void* buf, size_t size);
/** /**
* @brief read * @brief read
@ -245,7 +234,7 @@ public:
* @param dst * @param dst
* @param size * @param size
*/ */
ECardResult read(FileHandle& fh, void* dst, size_t size); ECardResult asyncRead(FileHandle& fh, void* dst, size_t size);
/** /**
* @brief seek * @brief seek
@ -336,6 +325,7 @@ public:
*/ */
ECardResult setStatus(uint32_t fileNo, const CardStat& stat); 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 * @brief Copies a file from the current Card instance to a specified Card instance
* @param fh The file to copy * @param fh The file to copy
@ -351,6 +341,7 @@ public:
* @return * @return
*/ */
bool moveFileTo(FileHandle& fh, Card& dest); 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 * @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(); 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 * @brief Commits changes to disk and closes host file
*/ */

View File

@ -250,71 +250,6 @@ typedef struct stat Sstat;
uint64_t getGCTime(); 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) #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif #endif
@ -349,6 +284,26 @@ static inline int Stat(const SystemChar* path, Sstat* statOut)
* @param checksumInv * @param checksumInv
*/ */
void calculateChecksumBE(const uint16_t* data, size_t len, uint16_t* checksum, uint16_t* 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__ #endif // __KABU_UTIL_HPP__

View File

@ -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<void*>(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<AsyncIO*>(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<AsyncIO*>(this)->m_queue.begin();
it != const_cast<AsyncIO*>(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<AsyncIO*>(this)->m_maxBlock = 0;
return result;
}
void AsyncIO::waitForCompletion() const
{
for (auto it = const_cast<AsyncIO*>(this)->m_queue.begin();
it != const_cast<AsyncIO*>(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;
}
}
}
}

View File

@ -0,0 +1,4 @@
//
// Created by Jack Andersen on 2/5/18.
//

View File

@ -15,7 +15,7 @@ static void NullFileAccess()
fprintf(stderr, "Attempted to access null file\n"); fprintf(stderr, "Attempted to access null file\n");
} }
void Card::_swapEndian() void Card::CardHeader::_swapEndian()
{ {
m_formatTime = SBig(m_formatTime); m_formatTime = SBig(m_formatTime);
m_sramBias = SBig(m_sramBias); m_sramBias = SBig(m_sramBias);
@ -35,8 +35,7 @@ Card::Card(Card&& other)
{ {
memmove(__raw, other.__raw, BlockSize); memmove(__raw, other.__raw, BlockSize);
m_filename = std::move(other.m_filename); m_filename = std::move(other.m_filename);
m_fileHandle = other.m_fileHandle; m_fileHandle = std::move(other.m_fileHandle);
other.m_fileHandle = nullptr;
m_dirs[0] = std::move(other.m_dirs[0]); m_dirs[0] = std::move(other.m_dirs[0]);
m_dirs[1] = std::move(other.m_dirs[1]); m_dirs[1] = std::move(other.m_dirs[1]);
m_bats[0] = std::move(other.m_bats[0]); m_bats[0] = std::move(other.m_bats[0]);
@ -55,8 +54,7 @@ Card& Card::operator=(Card&& other)
memmove(__raw, other.__raw, BlockSize); memmove(__raw, other.__raw, BlockSize);
m_filename = std::move(other.m_filename); m_filename = std::move(other.m_filename);
m_fileHandle = other.m_fileHandle; m_fileHandle = std::move(other.m_fileHandle);
other.m_fileHandle = nullptr;
m_dirs[0] = std::move(other.m_dirs[0]); m_dirs[0] = std::move(other.m_dirs[0]);
m_dirs[1] = std::move(other.m_dirs[1]); m_dirs[1] = std::move(other.m_dirs[1]);
m_bats[0] = std::move(other.m_bats[0]); m_bats[0] = std::move(other.m_bats[0]);
@ -71,24 +69,20 @@ Card& Card::operator=(Card&& other)
return *this; return *this;
} }
Card::Card(SystemStringView filename, const char* game, const char* maker) : m_filename(filename) ECardResult Card::_pumpOpen()
{ {
memset(__raw, 0xFF, BlockSize); if (m_opened)
if (game && strlen(game) == 4) return ECardResult::READY;
memcpy(m_game, game, 4);
if (maker && strlen(maker) == 2)
memcpy(m_maker, maker, 2);
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_ch._swapEndian();
m_maxBlock = m_sizeMb * MbitToBlocks; m_maxBlock = m_ch.m_sizeMb * MbitToBlocks;
_swapEndian(); m_fileHandle.resizeQueue(m_maxBlock);
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_dirs[0].swapEndian(); m_dirs[0].swapEndian();
m_dirs[1].swapEndian(); m_dirs[1].swapEndian();
@ -115,11 +109,19 @@ Card::Card(SystemStringView filename, const char* game, const char* maker) : m_f
else else
m_currentBat = 1; m_currentBat = 1;
/* Close and reopen in read/write mode */ m_opened = true;
fclose(m_fileHandle);
m_fileHandle = Fopen(m_filename.c_str(), _S("r+b"));
rewind(m_fileHandle);
} }
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() Card::~Card()
@ -129,6 +131,10 @@ Card::~Card()
ECardResult Card::openFile(const char* filename, FileHandle& handleOut) ECardResult Card::openFile(const char* filename, FileHandle& handleOut)
{ {
ECardResult openRes = _pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
handleOut = {}; handleOut = {};
File* f = m_dirs[m_currentDir].getFile(m_game, m_maker, filename); File* f = m_dirs[m_currentDir].getFile(m_game, m_maker, filename);
if (!f || f->m_game[0] == 0xFF) 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 Card::openFile(uint32_t fileno, FileHandle& handleOut)
{ {
ECardResult openRes = _pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
handleOut = {}; handleOut = {};
File* f = m_dirs[m_currentDir].getFile(fileno); File* f = m_dirs[m_currentDir].getFile(fileno);
if (!f || f->m_game[0] == 0xFF) if (!f || f->m_game[0] == 0xFF)
@ -165,13 +175,15 @@ void Card::_updateDirAndBat(const Directory& dir, const BlockAllocationTable& ba
updateBat = bat; updateBat = bat;
updateBat.m_updateCounter++; updateBat.m_updateCounter++;
updateBat.updateChecksum(); updateBat.updateChecksum();
m_dirty = true;
} }
void Card::_updateChecksum() void Card::_updateChecksum()
{ {
_swapEndian(); m_ch._swapEndian();
calculateChecksumBE(reinterpret_cast<uint16_t*>(__raw), 0xFE, &m_checksum, &m_checksumInv); calculateChecksumBE(reinterpret_cast<uint16_t*>(__raw), 0xFE, &m_ch.m_checksum, &m_ch.m_checksumInv);
_swapEndian(); m_ch._swapEndian();
} }
File* Card::_fileFromHandle(const FileHandle& fh) const 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, ECardResult Card::createFile(const char* filename, size_t size,
FileHandle& handleOut) FileHandle& handleOut)
{ {
ECardResult openRes = _pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
handleOut = {}; handleOut = {};
if (size <= 0) if (size <= 0)
@ -283,6 +299,10 @@ void Card::deleteFile(const FileHandle& fh)
ECardResult Card::deleteFile(const char* filename) ECardResult Card::deleteFile(const char* filename)
{ {
ECardResult openRes = _pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
Directory dir = m_dirs[m_currentDir]; Directory dir = m_dirs[m_currentDir];
File* f = dir.getFile(m_game, m_maker, filename); File* f = dir.getFile(m_game, m_maker, filename);
if (!f) if (!f)
@ -296,6 +316,10 @@ ECardResult Card::deleteFile(const char* filename)
ECardResult Card::deleteFile(uint32_t fileno) ECardResult Card::deleteFile(uint32_t fileno)
{ {
ECardResult openRes = _pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
Directory dir = m_dirs[m_currentDir]; Directory dir = m_dirs[m_currentDir];
File* f = dir.getFile(fileno); File* f = dir.getFile(fileno);
if (!f) if (!f)
@ -309,6 +333,10 @@ ECardResult Card::deleteFile(uint32_t fileno)
ECardResult Card::renameFile(const char* oldName, const char* newName) ECardResult Card::renameFile(const char* oldName, const char* newName)
{ {
ECardResult openRes = _pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
if (strlen(newName) > 32) if (strlen(newName) > 32)
return ECardResult::NAMETOOLONG; return ECardResult::NAMETOOLONG;
@ -332,10 +360,12 @@ ECardResult Card::renameFile(const char* oldName, const char* newName)
return ECardResult::READY; 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(); NullFileAccess();
@ -367,9 +397,8 @@ ECardResult Card::write(FileHandle& fh, const void* buf, size_t size)
if (cacheSize + blockOffset > BlockSize) if (cacheSize + blockOffset > BlockSize)
cacheSize = BlockSize - blockOffset; cacheSize = BlockSize - blockOffset;
uint32_t offset = (curBlock * BlockSize) + blockOffset; uint32_t offset = (curBlock * BlockSize) + blockOffset;
fseek(m_fileHandle, offset, SEEK_SET); if (!m_fileHandle.asyncWrite(curBlock, tmpBuf, cacheSize, offset))
if (fwrite(tmpBuf, 1, cacheSize, m_fileHandle) != cacheSize) return ECardResult::FATAL_ERROR;
return ECardResult::IOERROR;
tmpBuf += cacheSize; tmpBuf += cacheSize;
rem -= cacheSize; rem -= cacheSize;
blockOffset += cacheSize; blockOffset += cacheSize;
@ -380,15 +409,16 @@ ECardResult Card::write(FileHandle& fh, const void* buf, size_t size)
} }
} }
fh.offset += size; fh.offset += size;
}
return ECardResult::READY; 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(); NullFileAccess();
@ -419,9 +449,8 @@ ECardResult Card::read(FileHandle& fh, void* dst, size_t size)
if (cacheSize + blockOffset > BlockSize) if (cacheSize + blockOffset > BlockSize)
cacheSize = BlockSize - blockOffset; cacheSize = BlockSize - blockOffset;
uint32_t offset = (curBlock * BlockSize) + blockOffset; uint32_t offset = (curBlock * BlockSize) + blockOffset;
fseek(m_fileHandle, offset, SEEK_SET); if (!m_fileHandle.asyncRead(curBlock, tmpBuf, cacheSize, offset))
if (fread(tmpBuf, 1, cacheSize, m_fileHandle) != cacheSize) return ECardResult::FATAL_ERROR;
return ECardResult::IOERROR;
tmpBuf += cacheSize; tmpBuf += cacheSize;
rem -= cacheSize; rem -= cacheSize;
blockOffset += cacheSize; blockOffset += cacheSize;
@ -432,7 +461,6 @@ ECardResult Card::read(FileHandle& fh, void* dst, size_t size)
} }
} }
fh.offset += size; fh.offset += size;
}
return ECardResult::READY; 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 Card::getStatus(uint32_t fileNo, CardStat& statOut) const
{ {
ECardResult openRes = const_cast<Card*>(this)->_pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
const File* file = const_cast<Directory&>(m_dirs[m_currentDir]).getFile(fileNo); const File* file = const_cast<Directory&>(m_dirs[m_currentDir]).getFile(fileNo);
if (!file || file->m_game[0] == 0xFF) if (!file || file->m_game[0] == 0xFF)
return ECardResult::NOFILE; 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 Card::setStatus(uint32_t fileNo, const CardStat& stat)
{ {
ECardResult openRes = _pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
Directory dir = m_dirs[m_currentDir]; Directory dir = m_dirs[m_currentDir];
File* file = dir.getFile(fileNo); File* file = dir.getFile(fileNo);
if (!file || file->m_game[0] == 0xFF) if (!file || file->m_game[0] == 0xFF)
@ -664,6 +700,7 @@ ECardResult Card::setStatus(uint32_t fileNo, const CardStat& stat)
return ECardResult::READY; return ECardResult::READY;
} }
#if 0 // TODO: Async-friendly implementations
bool Card::copyFileTo(FileHandle& fh, Card& dest) bool Card::copyFileTo(FileHandle& fh, Card& dest)
{ {
if (!canCopy(fh)) if (!canCopy(fh))
@ -722,6 +759,7 @@ bool Card::moveFileTo(FileHandle& fh, Card& dest)
return false; return false;
} }
#endif
void Card::setCurrentGame(const char* game) void Card::setCurrentGame(const char* game)
{ {
@ -768,19 +806,19 @@ const uint8_t* Card::getCurrentMaker() const
void Card::getSerial(uint64_t& serial) void Card::getSerial(uint64_t& serial)
{ {
_swapEndian(); m_ch._swapEndian();
uint32_t serialBuf[8]; uint32_t serialBuf[8];
for (uint32_t i = 0; i < 8; i++) for (uint32_t i = 0; i < 8; i++)
serialBuf[i] = SBig(*reinterpret_cast<uint32_t*>(__raw + (i * 4))); serialBuf[i] = SBig(*reinterpret_cast<uint32_t*>(__raw + (i * 4)));
serial = uint64_t(serialBuf[0] ^ serialBuf[2] ^ serialBuf[4] ^ serialBuf[6]) << 32 | serial = uint64_t(serialBuf[0] ^ serialBuf[2] ^ serialBuf[4] ^ serialBuf[6]) << 32 |
(serialBuf[1] ^ serialBuf[3] ^ serialBuf[5] ^ serialBuf[7]); (serialBuf[1] ^ serialBuf[3] ^ serialBuf[5] ^ serialBuf[7]);
_swapEndian(); m_ch._swapEndian();
} }
void Card::getChecksum(uint16_t& checksum, uint16_t& inverse) void Card::getChecksum(uint16_t& checksum, uint16_t& inverse)
{ {
checksum = m_checksum; checksum = m_ch.m_checksum;
inverse = m_checksumInv; inverse = m_ch.m_checksumInv;
} }
void Card::getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) 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(); filesNotUsed = m_dirs[m_currentDir].numFreeFiles();
} }
static std::unique_ptr<uint8_t[]> DummyBlock;
void Card::format(ECardSlot id, ECardSize size, EEncoding encoding) void Card::format(ECardSlot id, ECardSize size, EEncoding encoding)
{ {
memset(__raw, 0xFF, BlockSize); memset(__raw, 0xFF, BlockSize);
uint64_t rand = uint64_t(getGCTime()); uint64_t rand = uint64_t(getGCTime());
m_formatTime = rand; m_ch.m_formatTime = rand;
for (int i = 0; i < 12; i++) for (int i = 0; i < 12; i++)
{ {
rand = (((rand * uint64_t(0x41c64e6d)) + uint64_t(0x3039)) >> 16); 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 = (((rand * uint64_t(0x41c64e6d)) + uint64_t(0x3039)) >> 16);
rand &= uint64_t(0x7fffULL); rand &= uint64_t(0x7fffULL);
} }
m_sramBias = int32_t(SBig(g_SRAM.counter_bias)); m_ch.m_sramBias = int32_t(SBig(g_SRAM.counter_bias));
m_sramLanguage = uint32_t(SBig(g_SRAM.lang)); m_ch.m_sramLanguage = uint32_t(SBig(g_SRAM.lang));
m_unknown = 0; /* 1 works for slot A, 0 both */ m_ch.m_unknown = 0; /* 1 works for slot A, 0 both */
m_deviceId = 0; m_ch.m_deviceId = 0;
m_sizeMb = uint16_t(size); m_ch.m_sizeMb = uint16_t(size);
m_maxBlock = m_sizeMb * MbitToBlocks; m_maxBlock = m_ch.m_sizeMb * MbitToBlocks;
m_encoding = uint16_t(encoding); m_ch.m_encoding = uint16_t(encoding);
_updateChecksum(); _updateChecksum();
m_dirs[0] = Directory(); m_dirs[0] = Directory();
m_dirs[1] = m_dirs[0]; m_dirs[1] = m_dirs[0];
@ -817,34 +857,35 @@ void Card::format(ECardSlot id, ECardSize size, EEncoding encoding)
m_currentDir = 1; m_currentDir = 1;
m_currentBat = 1; m_currentBat = 1;
if (m_fileHandle) m_fileHandle = {};
fclose(m_fileHandle); m_fileHandle = AsyncIO(m_filename.c_str(), true);
m_fileHandle = Fopen(m_filename.c_str(), _S("wb"));
if (m_fileHandle) if (m_fileHandle)
{ {
_swapEndian(); m_tmpCh = m_ch;
fwrite(__raw, 1, BlockSize, m_fileHandle); m_tmpCh._swapEndian();
_swapEndian(); m_fileHandle.asyncWrite(0, &m_tmpCh, BlockSize, 0);
Directory tmpDir = m_dirs[0]; m_tmpDirs[0] = m_dirs[0];
tmpDir.swapEndian(); m_tmpDirs[0].swapEndian();
fwrite(tmpDir.__raw, 1, BlockSize, m_fileHandle); m_fileHandle.asyncWrite(1, m_tmpDirs[0].__raw, BlockSize, BlockSize * 1);
tmpDir = m_dirs[1]; m_tmpDirs[1] = m_dirs[1];
tmpDir.swapEndian(); m_tmpDirs[1].swapEndian();
fwrite(tmpDir.__raw, 1, BlockSize, m_fileHandle); m_fileHandle.asyncWrite(2, m_tmpDirs[1].__raw, BlockSize, BlockSize * 2);
BlockAllocationTable tmpBat = m_bats[0]; m_tmpBats[0] = m_bats[0];
tmpBat.swapEndian(); m_tmpBats[0].swapEndian();
fwrite(tmpBat.__raw, 1, BlockSize, m_fileHandle); m_fileHandle.asyncWrite(3, m_tmpBats[0].__raw, BlockSize, BlockSize * 3);
tmpBat = m_bats[1]; m_tmpBats[1] = m_bats[1];
tmpBat.swapEndian(); m_tmpBats[1].swapEndian();
fwrite(tmpBat.__raw, 1, BlockSize, m_fileHandle); m_fileHandle.asyncWrite(4, m_tmpBats[1].__raw, BlockSize, BlockSize * 4);
uint32_t dataLen = ((uint32_t(size) * MbitToBlocks) - 5) * BlockSize; if (!DummyBlock)
std::unique_ptr<uint8_t[]> data(new uint8_t[dataLen]); {
memset(data.get(), 0xFF, dataLen); DummyBlock.reset(new uint8_t[BlockSize]);
fwrite(data.get(), 1, dataLen, m_fileHandle); memset(DummyBlock.get(), 0xFF, BlockSize);
fclose(m_fileHandle); }
m_fileHandle = Fopen(m_filename.c_str(), _S("r+b")); uint32_t blockCount = (uint32_t(size) * MbitToBlocks) - 5;
for (uint32_t i=0 ; i<blockCount ; ++i)
m_fileHandle.asyncWrite(i + 5, DummyBlock.get(), BlockSize, BlockSize * (i + 5));
m_dirty = false;
} }
} }
@ -858,54 +899,80 @@ ProbeResults Card::probeCardFile(SystemStringView filename)
void Card::commit() void Card::commit()
{ {
if (!m_dirty)
return;
if (m_fileHandle) if (m_fileHandle)
{ {
rewind(m_fileHandle); m_tmpCh = m_ch;
m_tmpCh._swapEndian();
_swapEndian(); m_fileHandle.asyncWrite(0, &m_tmpCh, BlockSize, 0);
fwrite(__raw, 1, BlockSize, m_fileHandle); m_tmpDirs[0] = m_dirs[0];
_swapEndian(); m_tmpDirs[0].updateChecksum();
Directory tmpDir = m_dirs[0]; m_tmpDirs[0].swapEndian();
tmpDir.updateChecksum(); m_fileHandle.asyncWrite(1, m_tmpDirs[0].__raw, BlockSize, BlockSize * 1);
tmpDir.swapEndian(); m_tmpDirs[1] = m_dirs[1];
fwrite(tmpDir.__raw, 1, BlockSize, m_fileHandle); m_tmpDirs[1].updateChecksum();
tmpDir = m_dirs[1]; m_tmpDirs[1].swapEndian();
tmpDir.updateChecksum(); m_fileHandle.asyncWrite(2, m_tmpDirs[1].__raw, BlockSize, BlockSize * 2);
tmpDir.swapEndian(); m_tmpBats[0] = m_bats[0];
fwrite(tmpDir.__raw, 1, BlockSize, m_fileHandle); m_tmpBats[0].updateChecksum();
BlockAllocationTable tmpBat = m_bats[0]; m_tmpBats[0].swapEndian();
tmpBat.updateChecksum(); m_fileHandle.asyncWrite(3, m_tmpBats[0].__raw, BlockSize, BlockSize * 3);
tmpBat.swapEndian(); m_tmpBats[1] = m_bats[1];
fwrite(tmpBat.__raw, 1, BlockSize, m_fileHandle); m_tmpBats[1].updateChecksum();
tmpBat = m_bats[1]; m_tmpBats[1].swapEndian();
tmpBat.updateChecksum(); m_fileHandle.asyncWrite(4, m_tmpBats[1].__raw, BlockSize, BlockSize * 4);
tmpBat.swapEndian(); m_dirty = false;
fwrite(tmpBat.__raw, 1, BlockSize, m_fileHandle);
fflush(m_fileHandle);
} }
} }
bool Card::open(SystemStringView filepath)
{
m_opened = false;
m_filename = filepath;
m_fileHandle = AsyncIO(m_filename);
if (m_fileHandle)
{
m_fileHandle.resizeQueue(5);
m_fileHandle.asyncRead(0, __raw, BlockSize, 0);
m_fileHandle.asyncRead(1, m_dirs[0].__raw, BlockSize, BlockSize * 1);
m_fileHandle.asyncRead(2, m_dirs[1].__raw, BlockSize, BlockSize * 2);
m_fileHandle.asyncRead(3, m_bats[0].__raw, BlockSize, BlockSize * 3);
m_fileHandle.asyncRead(4, m_bats[1].__raw, BlockSize, BlockSize * 4);
return true;
}
return false;
}
void Card::close() void Card::close()
{ {
m_opened = false;
if (m_fileHandle) if (m_fileHandle)
{ {
commit(); commit();
fclose(m_fileHandle); m_fileHandle.waitForCompletion();
m_fileHandle = nullptr; m_fileHandle = {};
} }
} }
ECardResult Card::getError() const ECardResult Card::getError() const
{ {
if (m_fileHandle == nullptr) if (!m_fileHandle)
return ECardResult::NOCARD; return ECardResult::NOCARD;
ECardResult pollRes = m_fileHandle.pollStatus();
if (pollRes != ECardResult::READY)
return pollRes;
ECardResult openRes = const_cast<Card*>(this)->_pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
uint16_t ckSum, ckSumInv; uint16_t ckSum, ckSumInv;
const_cast<Card&>(*this)._swapEndian(); const_cast<Card&>(*this).m_ch._swapEndian();
calculateChecksumBE(reinterpret_cast<const uint16_t*>(__raw), 0xFE, &ckSum, &ckSumInv); calculateChecksumBE(reinterpret_cast<const uint16_t*>(__raw), 0xFE, &ckSum, &ckSumInv);
bool res = (ckSum == m_checksum && ckSumInv == m_checksumInv); bool res = (ckSum == m_ch.m_checksum && ckSumInv == m_ch.m_checksumInv);
const_cast<Card&>(*this)._swapEndian(); const_cast<Card&>(*this).m_ch._swapEndian();
if (!res) if (!res)
return ECardResult::BROKEN; return ECardResult::BROKEN;

View File

@ -23,9 +23,9 @@ int main()
mc.setCanMove(f, true); mc.setCanMove(f, true);
kabufuda::CardStat stat = {}; kabufuda::CardStat stat = {};
mc.setStatus(f, 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.seek(f, 32, kabufuda::SeekOrigin::Begin);
mc.write(f, "Test\0", strlen("Test") + 1); mc.asyncWrite(f, "Test\0", strlen("Test") + 1);
} }
return 0; return 0;
} }