diff --git a/include/kabufuda/BlockAllocationTable.hpp b/include/kabufuda/BlockAllocationTable.hpp index 39a7823..fc2b3a1 100644 --- a/include/kabufuda/BlockAllocationTable.hpp +++ b/include/kabufuda/BlockAllocationTable.hpp @@ -36,6 +36,7 @@ public: uint16_t nextFreeBlock(uint16_t maxBlock, uint16_t startingBlock) const; bool clear(uint16_t first, uint16_t count); uint16_t allocateBlocks(uint16_t count, uint16_t maxBlocks); + uint16_t numFreeBlocks() const { return m_freeBlocks; } }; } #endif // __KABU_BLOCKALLOCATIONATABLE_HPP__ diff --git a/include/kabufuda/Card.hpp b/include/kabufuda/Card.hpp index 1aa9ea7..3ef6793 100644 --- a/include/kabufuda/Card.hpp +++ b/include/kabufuda/Card.hpp @@ -10,15 +10,94 @@ #include #include +#define CARD_FILENAME_MAX 32 +#define CARD_ICON_MAX 8 +#undef NOFILE + namespace kabufuda { class IFileHandle { +protected: + uint32_t idx; + IFileHandle() = default; + IFileHandle(uint32_t idx) : idx(idx) {} public: + uint32_t getFileNo() const { return idx; } virtual ~IFileHandle(); }; +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; + uint32_t x4_cardSize; /* in megabits */ + uint32_t x8_sectorSize; /* in bytes */ +}; + +struct CardStat +{ + /* read-only (Set by CARDGetStatus) */ + char x0_fileName[CARD_FILENAME_MAX]; + uint32_t x20_length; + uint32_t x24_time; /* seconds since 01/01/2000 midnight */ + uint8_t x28_gameName[4]; + uint8_t x2c_company[2]; + + /* read/write (Set by CARDGetStatus/CARDSetStatus) */ + uint8_t x2e_bannerFormat; + uint8_t x2f___padding; + uint32_t x30_iconAddr; /* offset to the banner, bannerTlut, icon, iconTlut data set. */ + uint16_t x34_iconFormat; + uint16_t x36_iconSpeed; + uint32_t x38_commentAddr; /* offset to the pair of 32 byte character strings. */ + + /* read-only (Set by CARDGetStatus) */ + uint32_t x3c_offsetBanner; + uint32_t x40_offsetBannerTlut; + uint32_t x44_offsetIcon[CARD_ICON_MAX]; + uint32_t x64_offsetIconTlut; + uint32_t x68_offsetData; + + uint32_t GetFileLength() const { return x20_length; } + uint32_t GetTime() const { return x24_time; } + EImageFormat GetBannerFormat() const { return EImageFormat(x2e_bannerFormat & 0x3); } + void SetBannerFormat(EImageFormat fmt) { x2e_bannerFormat = (x2e_bannerFormat & ~0x3) | uint8_t(fmt); } + EImageFormat GetIconFormat(int idx) const { return EImageFormat((x34_iconFormat >> (idx * 2)) & 0x3); } + void SetIconFormat(EImageFormat fmt, int idx) + { + x34_iconFormat &= ~(0x3 << (idx * 2)); + x34_iconFormat |= uint16_t(fmt) << (idx * 2); + } + void SetIconSpeed(EAnimationSpeed sp, int idx) + { + x36_iconSpeed &= ~(0x3 << (idx * 2)); + x36_iconSpeed |= uint16_t(sp) << (idx * 2); + } + uint32_t GetIconAddr() const { return x30_iconAddr; } + void SetIconAddr(uint32_t addr) { x30_iconAddr = addr; } + uint32_t GetCommentAddr() const { return x38_commentAddr; } + void SetCommentAddr(uint32_t addr) { x38_commentAddr = addr; } +}; + class Card { #pragma pack(push, 4) @@ -62,6 +141,7 @@ class Card void _updateDirAndBat(); void _updateChecksum(); File* _fileFromHandle(const std::unique_ptr& fh) const; + void _deleteFile(File& f); public: Card(); @@ -85,12 +165,18 @@ public: */ std::unique_ptr openFile(const char* filename); + /** + * @brief openFile + * @param fileno + */ + std::unique_ptr openFile(uint32_t fileno); + /** * @brief createFile * @param filename * @return */ - std::unique_ptr createFile(const char* filename, size_t size); + ECardResult createFile(const char* filename, size_t size, std::unique_ptr& handleOut); /** * @brief firstFile @@ -104,18 +190,39 @@ public: * @return */ std::unique_ptr nextFile(const std::unique_ptr& cur); + /** * @brief getFilename * @param fh * @return */ const char* getFilename(const std::unique_ptr& fh); + /** * @brief deleteFile * @param fh */ void deleteFile(const std::unique_ptr& fh); + /** + * @brief deleteFile + * @param filename + */ + ECardResult deleteFile(const char* filename); + + /** + * @brief deleteFile + * @param fileno + */ + ECardResult deleteFile(uint32_t fileno); + + /** + * @brief renameFile + * @param oldName + * @param newName + */ + ECardResult renameFile(const char* oldName, const char* newName); + /** * @brief write * @param fh @@ -344,21 +451,27 @@ public: * @param checksum The checksum of the system header * @param inverse The inverser checksum of the system header */ - void getChecksum(uint16_t* checksum, uint16_t* inverse); + void getChecksum(uint16_t& checksum, uint16_t& inverse); + + /** + * @brief Retrieves the available storage and directory space + * @param bytesNotUsed Number of free bytes out + * @param filesNotUsed Number of free files out + */ + void getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed); /** * @brief Formats the memory card and assigns a new serial * @param size The desired size of the file @sa ECardSize * @param encoding The desired encoding @sa EEncoding */ - void format(EDeviceId deviceId, ECardSize size = ECardSize::Card2043Mb, EEncoding encoding = EEncoding::ASCII); + void format(ECardSlot deviceId, ECardSize size = ECardSize::Card2043Mb, EEncoding encoding = EEncoding::ASCII); /** - * @brief Returns the size of the file in Megabits from a file on disk, useful for determining filesize ahead of - * time. - * @return Size of file in Megabits + * @brief Returns basic stats about a card image without opening a handle + * @return ProbeResults structure */ - static uint32_t getSizeMbitFromFile(const SystemString& filename); + static ProbeResults probeCardFile(const SystemString& filename); /** * @brief Writes any changes to the Card instance immediately to disk.
@@ -366,7 +479,13 @@ public: */ void commit(); - operator bool() const; + /** + * @brief Gets card-scope error state + * @return READY, BROKEN, or NOCARD + */ + ECardResult getError() const; + + operator bool() const { return getError() == ECardResult::READY; } }; } diff --git a/include/kabufuda/Constants.hpp b/include/kabufuda/Constants.hpp index 1c93e86..730dc88 100644 --- a/include/kabufuda/Constants.hpp +++ b/include/kabufuda/Constants.hpp @@ -54,9 +54,9 @@ enum class SeekOrigin }; /** - * @brief The EDeviceId enum + * @brief The ECardSlot enum */ -enum class EDeviceId : uint16_t +enum class ECardSlot : uint16_t { SlotA, SlotB diff --git a/include/kabufuda/Directory.hpp b/include/kabufuda/Directory.hpp index 3e209f6..9827a48 100644 --- a/include/kabufuda/Directory.hpp +++ b/include/kabufuda/Directory.hpp @@ -33,6 +33,8 @@ public: void operator=(const Directory& other); ~Directory(); + bool hasFreeFile() const; + int32_t numFreeFiles() const; File* getFirstFreeFile(const char* game, const char* maker, const char* filename); File* getFirstNonFreeFile(uint32_t start, const char* game, const char* maker); File* getFile(const char* game, const char* maker, const char* filename); diff --git a/include/kabufuda/Util.hpp b/include/kabufuda/Util.hpp index 2b5129b..e61909d 100644 --- a/include/kabufuda/Util.hpp +++ b/include/kabufuda/Util.hpp @@ -127,7 +127,9 @@ static inline double SBig(double val) int64_t ival = bswap64(*((int64_t*)(&val))); return *((double*)(&ival)); } +#ifndef SBIG #define SBIG(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24) +#endif static inline int16_t SLittle(int16_t val) { return val; } static inline uint16_t SLittle(uint16_t val) { return val; } @@ -137,7 +139,9 @@ static inline int64_t SLittle(int64_t val) { return val; } static inline uint64_t SLittle(uint64_t val) { return val; } static inline float SLittle(float val) { return val; } static inline double SLittle(double val) { return val; } +#ifndef SLITTLE #define SLITTLE(q) (q) +#endif #else static inline int16_t SLittle(int16_t val) { return bswap16(val); } static inline uint16_t SLittle(uint16_t val) { return bswap16(val); } @@ -155,7 +159,9 @@ static inline double SLittle(double val) int64_t ival = bswap64(*((int64_t*)(&val))); return *((double*)(&ival)); } +#ifndef SLITTLE #define SLITTLE(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24) +#endif static inline int16_t SBig(int16_t val) { return val; } static inline uint16_t SBig(uint16_t val) { return val; } @@ -165,8 +171,10 @@ static inline int64_t SBig(int64_t val) { return val; } static inline uint64_t SBig(uint64_t val) { return val; } static inline float SBig(float val) { return val; } static inline double SBig(double val) { return val; } +#ifndef SBIG #define SBIG(q) (q) #endif +#endif #if CARD_UCS2 typedef wchar_t SystemChar; diff --git a/lib/kabufuda/BlockAllocationTable.cpp b/lib/kabufuda/BlockAllocationTable.cpp index dd1b93e..ae700fb 100644 --- a/lib/kabufuda/BlockAllocationTable.cpp +++ b/lib/kabufuda/BlockAllocationTable.cpp @@ -43,7 +43,7 @@ BlockAllocationTable::~BlockAllocationTable() {} uint16_t BlockAllocationTable::getNextBlock(uint16_t block) const { if ((block < FSTBlocks) || (block > (BATSize - FSTBlocks))) - return 0; + return 0xFFFF; return m_map[block - FSTBlocks]; } diff --git a/lib/kabufuda/Card.cpp b/lib/kabufuda/Card.cpp index 15599bf..6b274df 100644 --- a/lib/kabufuda/Card.cpp +++ b/lib/kabufuda/Card.cpp @@ -8,17 +8,18 @@ namespace kabufuda { +#define ROUND_UP_8192(val) (((val) + 8191) & ~8191) + IFileHandle::~IFileHandle() {} class FileHandle : public IFileHandle { friend class Card; - uint32_t idx; int32_t offset = 0; public: FileHandle() = default; - FileHandle(uint32_t idx) : idx(idx) {} + FileHandle(uint32_t idx) : IFileHandle(idx) {} virtual ~FileHandle(); }; @@ -109,7 +110,6 @@ Card::Card(const SystemString& filename, const char* game, const char* maker) : /* Close and reopen in read/write mode */ fclose(m_fileHandle); - m_fileHandle = nullptr; m_fileHandle = Fopen(m_filename.c_str(), _S("r+")); rewind(m_fileHandle); } @@ -129,12 +129,18 @@ std::unique_ptr Card::openFile(const char* filename) int32_t idx = m_currentDir->indexForFile(f); if (f && idx != -1) { - - return std::unique_ptr(new FileHandle(idx)); + return std::make_unique(idx); } return nullptr; } +std::unique_ptr Card::openFile(uint32_t fileno) +{ + if (fileno >= 127) + return nullptr; + return std::make_unique(fileno); +} + void Card::_updateDirAndBat() { Directory updateDir = *m_currentDir; @@ -168,27 +174,40 @@ File* Card::_fileFromHandle(const std::unique_ptr& fh) const return file; } -std::unique_ptr Card::createFile(const char* filename, size_t size) +ECardResult Card::createFile(const char* filename, size_t size, + std::unique_ptr& handleOut) { + if (strlen(filename) > 32) + return ECardResult::NAMETOOLONG; + if (m_currentDir->getFile(m_game, m_maker, filename)) + return ECardResult::EXIST; + uint16_t neededBlocks = ROUND_UP_8192(size) / BlockSize; + if (neededBlocks > m_currentBat->numFreeBlocks()) + return ECardResult::INSSPACE; + if (!m_currentDir->hasFreeFile()) + return ECardResult::NOENT; + _updateDirAndBat(); File* f = m_currentDir->getFirstFreeFile(m_game, m_maker, filename); - uint16_t block = m_currentBat->allocateBlocks(uint16_t(size / BlockSize), m_maxBlock); + uint16_t block = m_currentBat->allocateBlocks(neededBlocks, m_maxBlock); if (f && block != 0xFFFF) { f->m_modifiedTime = uint32_t(getGCTime()); f->m_firstBlock = block; - f->m_blockCount = uint16_t(size / BlockSize); + f->m_blockCount = neededBlocks; - return std::unique_ptr(new FileHandle(m_currentDir->indexForFile(f))); + handleOut = std::make_unique(m_currentDir->indexForFile(f)); + return ECardResult::READY; } - return nullptr; + + return ECardResult::FATAL_ERROR; } std::unique_ptr Card::firstFile() { File* f = m_currentDir->getFirstNonFreeFile(0, m_game, m_maker); if (f) - return std::unique_ptr(new FileHandle(m_currentDir->indexForFile(f))); + return std::make_unique(m_currentDir->indexForFile(f)); return nullptr; } @@ -202,7 +221,7 @@ std::unique_ptr Card::nextFile(const std::unique_ptr& File* next = m_currentDir->getFirstNonFreeFile(handle->idx + 1, m_game, m_maker); if (!next) return nullptr; - return std::unique_ptr(new FileHandle(m_currentDir->indexForFile(next))); + return std::make_unique(m_currentDir->indexForFile(next)); } const char* Card::getFilename(const std::unique_ptr& fh) @@ -213,14 +232,9 @@ const char* Card::getFilename(const std::unique_ptr& fh) return f->m_filename; } -void Card::deleteFile(const std::unique_ptr& fh) +void Card::_deleteFile(File& f) { - _updateDirAndBat(); - if (!fh) - return; - FileHandle* f = dynamic_cast(fh.get()); - uint16_t block = m_currentDir->getFile(f->idx)->m_firstBlock; - + uint16_t block = f.m_firstBlock; while (block != 0xFFFF) { /* TODO: add a fragmentation check */ @@ -228,7 +242,52 @@ void Card::deleteFile(const std::unique_ptr& fh) m_currentBat->clear(block, 1); block = nextBlock; } - *m_currentDir->getFile(f->idx) = File(); + f = File(); +} + +void Card::deleteFile(const std::unique_ptr& fh) +{ + _updateDirAndBat(); + if (!fh) + return; + FileHandle* f = dynamic_cast(fh.get()); + _deleteFile(*m_currentDir->getFile(f->idx)); +} + +ECardResult Card::deleteFile(const char* filename) +{ + _updateDirAndBat(); + File* f = m_currentDir->getFile(m_game, m_maker, filename); + if (!f) + return ECardResult::NOFILE; + + _deleteFile(*f); + return ECardResult::READY; +} + +ECardResult Card::deleteFile(uint32_t fileno) +{ + _updateDirAndBat(); + File* f = m_currentDir->getFile(fileno); + if (!f) + return ECardResult::NOFILE; + + _deleteFile(*f); + return ECardResult::READY; +} + +ECardResult Card::renameFile(const char* oldName, const char* newName) +{ + if (strlen(newName) > 32) + return ECardResult::NAMETOOLONG; + + _updateDirAndBat(); + File* f = m_currentDir->getFile(m_game, m_maker, oldName); + if (!f) + return ECardResult::NOFILE; + + strncpy(f->m_filename, newName, 32); + return ECardResult::READY; } void Card::write(const std::unique_ptr& fh, const void* buf, size_t size) @@ -564,7 +623,8 @@ bool Card::copyFileTo(const std::unique_ptr& fh, Card& dest) return false; /* Try to allocate a new file */ - std::unique_ptr tmpHandle = dest.createFile(toCopy->m_filename, toCopy->m_blockCount * BlockSize); + std::unique_ptr tmpHandle; + dest.createFile(toCopy->m_filename, toCopy->m_blockCount * BlockSize, tmpHandle); if (!tmpHandle) return false; @@ -656,13 +716,19 @@ void Card::getSerial(uint64_t& serial) _swapEndian(); } -void Card::getChecksum(uint16_t* checksum, uint16_t* inverse) +void Card::getChecksum(uint16_t& checksum, uint16_t& inverse) { - *checksum = m_checksum; - *inverse = m_checksumInv; + checksum = m_checksum; + inverse = m_checksumInv; } -void Card::format(EDeviceId id, ECardSize size, EEncoding encoding) +void Card::getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) +{ + bytesNotUsed = m_currentBat->numFreeBlocks() * 0x2000; + filesNotUsed = m_currentDir->numFreeFiles(); +} + +void Card::format(ECardSlot id, ECardSize size, EEncoding encoding) { memset(__raw, 0xFF, BlockSize); uint64_t rand = uint64_t(getGCTime()); @@ -723,11 +789,12 @@ void Card::format(EDeviceId id, ECardSize size, EEncoding encoding) } } -uint32_t Card::getSizeMbitFromFile(const SystemString& filename) +ProbeResults Card::probeCardFile(const SystemString& filename) { Sstat stat; - Stat(filename.c_str(), &stat); - return uint32_t(stat.st_size / BlockSize) / MbitToBlocks; + if (Stat(filename.c_str(), &stat)) + return { ECardResult::NOCARD, 0, 0 }; + return { ECardResult::READY, uint32_t(stat.st_size / BlockSize) / MbitToBlocks, 0x2000 }; } void Card::commit() @@ -758,22 +825,22 @@ void Card::commit() } } -Card::operator bool() const +ECardResult Card::getError() const { if (m_fileHandle == nullptr) - return false; + return ECardResult::NOCARD; uint16_t ckSum, ckSumInv; Card tmp = *this; tmp._swapEndian(); calculateChecksumBE(reinterpret_cast(tmp.__raw), 0xFE, &ckSum, &ckSumInv); if (SBig(ckSum) != m_checksum || SBig(ckSumInv) != m_checksumInv) - return false; + return ECardResult::BROKEN; if (!m_dir.valid() && !m_dirBackup.valid()) - return false; + return ECardResult::BROKEN; if (!m_bat.valid() && !m_batBackup.valid()) - return false; + return ECardResult::BROKEN; - return true; + return ECardResult::READY; } } diff --git a/lib/kabufuda/Directory.cpp b/lib/kabufuda/Directory.cpp index 9881e6e..4408c5c 100644 --- a/lib/kabufuda/Directory.cpp +++ b/lib/kabufuda/Directory.cpp @@ -44,6 +44,23 @@ void Directory::operator=(const Directory& other) { memcpy(__raw, other.__raw, B Directory::~Directory() {} +bool Directory::hasFreeFile() const +{ + for (uint16_t i = 0; i < 127; i++) + if (m_files[i].m_game[0] == 0xFF) + return true; + return false; +} + +int32_t Directory::numFreeFiles() const +{ + int32_t ret = 0; + for (uint16_t i = 0; i < 127; i++) + if (m_files[i].m_game[0] == 0xFF) + ++ret; + return ret; +} + File* Directory::getFirstFreeFile(const char* game, const char* maker, const char* filename) { for (uint16_t i = 0; i < 127; i++) diff --git a/test/main.cpp b/test/main.cpp index e1e99f9..2638daa 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -5,18 +5,18 @@ int main() { kabufuda::Card mc{_S("test.USA.raw"), "GM8E", "01"}; if (!mc) - mc.format(kabufuda::EDeviceId::SlotA, kabufuda::ECardSize::Card2043Mb); + mc.format(kabufuda::ECardSlot::SlotA, kabufuda::ECardSize::Card2043Mb); uint64_t a = 0; mc.getSerial(a); kabufuda::Card mc2{_S("test2.USA.raw"), "GM8E", "01"}; if (!mc2) - mc2.format(kabufuda::EDeviceId::SlotA, kabufuda::ECardSize::Card2043Mb); + mc2.format(kabufuda::ECardSlot::SlotA, kabufuda::ECardSize::Card2043Mb); std::unique_ptr f = mc.openFile("MetroidPrime A"); if (!f) { - f = mc.createFile("MetroidPrime A", kabufuda::BlockSize); + mc.createFile("MetroidPrime A", kabufuda::BlockSize, f); mc.setPublic(f, true); mc.setCanCopy(f, true); mc.setCanMove(f, true);