From a8016716433fd47d97c3b8b4a8a09ecd877d6fce Mon Sep 17 00:00:00 2001 From: CraftyBoss <43161115+CraftyBoss@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:39:42 -0700 Subject: [PATCH] Card gci folder update (#135) * add ability to set path of card image before CARDInit * begin work on updating card class to support gci folders a lot of work will just be directly reading from whatever folder is supplied, instead of relying on an always open FileIO handle * swap to interface system for easier code seperation, begin actually planning out logic for gci loading * fix wrong name for func, add modified time to new gci file entry * get gci folder working tp seems to be happy with everything ive done, the only funcs im worried about are getSerial and getChecksum as those dont do anything atm. * fix createFile not assigning file index to output FileHandle, CARDSetLoadType now takes an enum * make probeCardFile a virtual func so GciFolder and RawFile can both implement their own, bit of cleanup --- cmake/aurora_card.cmake | 3 +- include/dolphin/card.h | 11 + lib/card/BlockAllocationTable.hpp | 2 +- lib/card/CardGciFolder.cpp | 405 +++++++++++++++++++++++++ lib/card/CardGciFolder.hpp | 77 +++++ lib/card/{Card.cpp => CardRawFile.cpp} | 147 ++++----- lib/card/{Card.hpp => CardRawFile.hpp} | 148 +++------ lib/card/CommonData.h | 74 +++++ lib/card/Constants.hpp | 39 +++ lib/card/Directory.hpp | 2 +- lib/card/File.hpp | 3 +- lib/card/FileIO.cpp | 18 ++ lib/card/FileIO.hpp | 2 + lib/card/ICard.hpp | 44 +++ lib/card/Util.hpp | 2 + lib/dolphin/card.cpp | 117 ++++--- 16 files changed, 844 insertions(+), 250 deletions(-) create mode 100644 lib/card/CardGciFolder.cpp create mode 100644 lib/card/CardGciFolder.hpp rename lib/card/{Card.cpp => CardRawFile.cpp} (85%) rename lib/card/{Card.hpp => CardRawFile.hpp} (71%) create mode 100644 lib/card/CommonData.h create mode 100644 lib/card/ICard.hpp diff --git a/cmake/aurora_card.cmake b/cmake/aurora_card.cmake index 97311f3..5eace0f 100644 --- a/cmake/aurora_card.cmake +++ b/cmake/aurora_card.cmake @@ -1,6 +1,7 @@ add_library(aurora_card STATIC lib/card/BlockAllocationTable.cpp - lib/card/Card.cpp + lib/card/CardRawFile.cpp + lib/card/CardGciFolder.cpp lib/card/Directory.cpp lib/card/DolphinCardPath.cpp lib/card/File.cpp diff --git a/include/dolphin/card.h b/include/dolphin/card.h index b3de481..d1fbb2f 100644 --- a/include/dolphin/card.h +++ b/include/dolphin/card.h @@ -1,6 +1,7 @@ #ifndef _DOLPHIN_CARD_H_ #define _DOLPHIN_CARD_H_ +#include #include #include #include @@ -13,6 +14,15 @@ extern "C" { #define CARD_MAX_FILE 127 #define CARD_ICON_MAX 8 +#if TARGET_PC + +enum CARDFileType { + RawImage, + GciFolder, +}; + +#endif + typedef void (*CARDCallback)(s32 chan, s32 result); typedef struct CARDFileInfo { @@ -240,6 +250,7 @@ void CARDSetGameAndMaker(const s32 chan, const char* game, const char* maker); void CARDDetectDolphin(s32 chan); // pass -1 to set both void CARDSetBasePath(const char*, s32 chan); +void CARDSetLoadType(CARDFileType type); #else void CARDInit(void); diff --git a/lib/card/BlockAllocationTable.hpp b/lib/card/BlockAllocationTable.hpp index b79e2aa..56f7a40 100644 --- a/lib/card/BlockAllocationTable.hpp +++ b/lib/card/BlockAllocationTable.hpp @@ -7,7 +7,7 @@ namespace aurora::card { class BlockAllocationTable { - friend class Card; + friend class CardRawFile; #pragma pack(push, 4) union { struct { diff --git a/lib/card/CardGciFolder.cpp b/lib/card/CardGciFolder.cpp new file mode 100644 index 0000000..567a674 --- /dev/null +++ b/lib/card/CardGciFolder.cpp @@ -0,0 +1,405 @@ +#include "CardGciFolder.hpp" + +#include + +#include "Directory.hpp" +#include "FileIO.hpp" +#include "../internal.hpp" + +namespace { +aurora::Module Log("aurora::card"); +} + +namespace aurora::card { + +CardGciFolder::GciFile* CardGciFolder::getFile(uint32_t idx) { + if (m_files.size() > idx) { + auto file = &m_files[idx]; + if (file->opened) + return file; + } + return nullptr; +} + +CardGciFolder::GciFile* CardGciFolder::getFile(FileHandle& fh) { return getFile(fh.getFileNo()); } + +const CardGciFolder::GciFile* CardGciFolder::getFile(uint32_t idx) const { + if (m_files.size() > idx) { + auto file = &m_files[idx]; + if (file->opened) + return file; + } + return nullptr; +} + +const CardGciFolder::GciFile* CardGciFolder::getFile(FileHandle& fh) const { return getFile(fh.getFileNo()); } + +CardGciFolder::CardGciFolder() {} + +CardGciFolder::CardGciFolder(CardGciFolder&& other) { + m_files = std::move(other.m_files); + m_bat = std::move(other.m_bat); + m_folderPath = other.m_folderPath; + m_encoding = other.m_encoding; + + CardGciFolder::setCurrentGame(other.m_game); + CardGciFolder::setCurrentMaker(other.m_maker); +} + +CardGciFolder& CardGciFolder::operator=(CardGciFolder&& other) { return *this; } + +void CardGciFolder::InitCard(const char* game, const char* maker) { + setCurrentGame(game); + setCurrentMaker(maker); +} + +ECardResult CardGciFolder::openFile(const char* filename, FileHandle& handleOut) { + int idx = 0; + for (auto& gciFile : m_files) { + if (strcmp(filename, gciFile.file.m_filename) == 0) { + gciFile.opened = true; + if (gciFile.fileSize == 0) + gciFile.fileSize = std::filesystem::file_size(m_folderPath / gciFile.filename); + + handleOut = FileHandle(idx, 0); + return ECardResult::READY; + } + idx++; + } + + return ECardResult::NOCARD; +} + +ECardResult CardGciFolder::openFile(uint32_t fileno, FileHandle& handleOut) { + if (m_files.size() > fileno) { + auto& gciFile = m_files[fileno]; + handleOut = FileHandle(fileno, 0); + gciFile.opened = true; + if (gciFile.fileSize == 0) + gciFile.fileSize = std::filesystem::file_size(m_folderPath / gciFile.filename); + + return ECardResult::READY; + } + + return ECardResult::NOCARD; +} + +ECardResult CardGciFolder::createFile(const char* filename, size_t size, FileHandle& handleOut) { + std::string gciFilename = fmt::format("{}-{}-{}.gci", m_maker, m_game, filename); + uint16_t neededBlocks = ROUND_UP_8192(size) / BlockSize; + size_t fileSize = sizeof(File) + size; + constexpr uint16_t maxBlocks = ((uint16_t)ECardSize::Card2043Mb * MbitToBlocks) - FSTBlocks; + + std::vector fileBuf(fileSize); + + File* gciFileHeader = (File*)fileBuf.data(); + + std::memcpy(gciFileHeader->m_game, m_game, 4); + std::memcpy(gciFileHeader->m_maker, m_maker, 2); + strncpy(gciFileHeader->m_filename, filename, std::size(gciFileHeader->m_filename)); + + gciFileHeader->m_modifiedTime = static_cast(getGCTime()); + gciFileHeader->m_blockCount = neededBlocks; + gciFileHeader->m_firstBlock = m_bat.allocateBlocks(neededBlocks, maxBlocks); + + m_files.push_back({*gciFileHeader, fileSize, gciFilename, false}); // push non-endian swapped header first + + handleOut = FileHandle(m_files.size() - 1, 0); + + // write big endian header for dolphin compat + gciFileHeader->swapEndian(); + FileIO file((m_folderPath / gciFilename).string(), true); + file.fileWrite(fileBuf.data(), fileBuf.size(), 0); + + return ECardResult::READY; +} + +ECardResult CardGciFolder::closeFile(FileHandle& fh) { + auto file = getFile(fh); + if (file) { + file->opened = false; + return ECardResult::READY; + } + + return ECardResult::NOFILE; +} + +void CardGciFolder::deleteFile(const FileHandle& fh) { + auto file = getFile(fh.getFileNo()); + if (!file) + return; + + FileIO fileIO((m_folderPath / file->filename).string(), true); + if (fileIO) + fileIO.deleteFile(); +} + +ECardResult CardGciFolder::deleteFile(const char* filename) { return ECardResult::NOCARD; } + +ECardResult CardGciFolder::deleteFile(uint32_t fileno) { return ECardResult::NOCARD; } + +ECardResult CardGciFolder::renameFile(const char* oldName, const char* newName) { + for (auto& gciFile : m_files) { + if (strcmp(oldName, gciFile.file.m_filename) == 0) { + strncpy(gciFile.file.m_filename, newName, std::size(gciFile.file.m_filename)); + return ECardResult::READY; + } + } + + return ECardResult::NOCARD; +} + +ECardResult CardGciFolder::fileWrite(FileHandle& fh, const void* buf, size_t size) { + auto file = getFile(fh); + if (file) { + FileIO fileIO((m_folderPath / file->filename).string()); + if (fileIO) { + if (fileIO.fileWrite(buf, size, sizeof(File) + fh.offset)) + return ECardResult::READY; + return ECardResult::IOERROR; + } + return ECardResult::NOFILE; + } + + return ECardResult::NOCARD; +} + +ECardResult CardGciFolder::fileRead(FileHandle& fh, void* dst, size_t size) { + auto file = getFile(fh); + if (file) { + FileIO fileIO((m_folderPath / file->filename).string()); + if (fileIO) { + if (fileIO.fileRead(dst, size, sizeof(File) + fh.offset)) + return ECardResult::READY; + return ECardResult::IOERROR; + } + return ECardResult::NOFILE; + } + + return ECardResult::NOCARD; +} + +void CardGciFolder::seek(FileHandle& fh, int32_t pos, SeekOrigin whence) { + auto file = getFile(fh); + if (file) { + switch (whence) { + case SeekOrigin::Begin: + fh.offset = pos; + break; + case SeekOrigin::Current: + fh.offset += pos; + break; + case SeekOrigin::End: + fh.offset = file->fileSize - pos; + break; + } + } +} + +ECardResult CardGciFolder::getStatus(const FileHandle& fh, CardStat& statOut) const { + return getStatus(fh.getFileNo(), statOut); +} + +ECardResult CardGciFolder::getStatus(uint32_t fileNo, CardStat& statOut) const { + auto gciFile = getFile(fileNo); + + if (!gciFile) + return ECardResult::NOFILE; + const auto file = &gciFile->file; + + std::strncpy(statOut.x0_fileName, file->m_filename, 32); + statOut.x20_length = file->m_blockCount * BlockSize; + statOut.x24_time = file->m_modifiedTime; + memmove(statOut.x28_gameName.data(), file->m_game, 4); + memmove(statOut.x2c_company.data(), file->m_maker, 4); + + statOut.x2e_bannerFormat = file->m_bannerFlags; + statOut.x30_iconAddr = file->m_iconAddress; + statOut.x34_iconFormat = file->m_iconFmt; + statOut.x36_iconSpeed = file->m_animSpeed; + statOut.x38_commentAddr = file->m_commentAddr; + + if (file->m_iconAddress == UINT32_MAX) { + statOut.x3c_offsetBanner = UINT32_MAX; + statOut.x40_offsetBannerTlut = UINT32_MAX; + statOut.x44_offsetIcon.fill(UINT32_MAX); + statOut.x64_offsetIconTlut = UINT32_MAX; + statOut.x68_offsetData = file->m_commentAddr + 64; + } else { + uint32_t cur = file->m_iconAddress; + statOut.x3c_offsetBanner = cur; + cur += BannerSize(statOut.GetBannerFormat()); + statOut.x40_offsetBannerTlut = cur; + cur += TlutSize(statOut.GetBannerFormat()); + bool palette = false; + for (size_t i = 0; i < statOut.x44_offsetIcon.size(); ++i) { + statOut.x44_offsetIcon[i] = cur; + const EImageFormat fmt = statOut.GetIconFormat(static_cast(i)); + if (fmt == EImageFormat::C8) { + palette = true; + } + cur += IconSize(fmt); + } + if (palette) { + statOut.x64_offsetIconTlut = cur; + cur += TlutSize(EImageFormat::C8); + } else + statOut.x64_offsetIconTlut = UINT32_MAX; + statOut.x68_offsetData = cur; + } + + return ECardResult::READY; +} + +ECardResult CardGciFolder::setStatus(const FileHandle& fh, const CardStat& stat) { + return setStatus(fh.getFileNo(), stat); +} + +ECardResult CardGciFolder::setStatus(uint32_t fileNo, const CardStat& stat) { + auto gciFile = getFile(fileNo); + + if (!gciFile) + return ECardResult::NOFILE; + const auto file = &gciFile->file; + + file->m_bannerFlags = stat.x2e_bannerFormat; + file->m_iconAddress = stat.x30_iconAddr; + file->m_iconFmt = stat.x34_iconFormat; + file->m_animSpeed = stat.x36_iconSpeed; + file->m_commentAddr = stat.x38_commentAddr; + + return ECardResult::READY; +} + +void CardGciFolder::setCurrentGame(const char* game) { + if (game != nullptr && std::strlen(game) == 4) { + std::memcpy(m_game, game, 4); + } +} + +const uint8_t* CardGciFolder::getCurrentGame() const { + if (std::strlen(m_game) == sizeof(m_game) - 1) { + return reinterpret_cast(m_game); + } + + return nullptr; +} + +void CardGciFolder::setCurrentMaker(const char* maker) { + if (maker != nullptr && std::strlen(maker) == 2) { + std::memcpy(m_maker, maker, 2); + } +} + +const uint8_t* CardGciFolder::getCurrentMaker() const { + if (std::strlen(m_maker) == sizeof(m_maker) - 1) { + return reinterpret_cast(m_maker); + } + + return nullptr; +} + +void CardGciFolder::getSerial(uint64_t& serial) { + serial = 0; // TODO +} + +void CardGciFolder::getChecksum(uint16_t& checksum, uint16_t& inverse) const { + // TODO + checksum = 0; + inverse = 0; +} + +void CardGciFolder::getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) const { + bytesNotUsed = m_bat.numFreeBlocks() * BlockSize; + filesNotUsed = MaxFiles; +} + +void CardGciFolder::getEncoding(uint16_t& encoding) const { encoding = (uint16_t)m_encoding; } + +void CardGciFolder::format(ECardSlot deviceId, ECardSize size, EEncoding encoding) { + m_encoding = encoding; + + if (!std::filesystem::create_directories(m_folderPath)) { + Log.error("Failed to create directory: {}", m_folderPath.string()); + } +} + +void CardGciFolder::commit() { + for (auto& gciFile : m_files) { + if (gciFile.opened) { + FileIO file((m_folderPath / gciFile.filename).string()); + + File tempFile = gciFile.file; + tempFile.swapEndian(); + file.fileWrite(&tempFile, sizeof(File), 0); // update header + } + } +} + +bool CardGciFolder::open(std::string_view filepath) { + m_folderPath = filepath; + + if (!std::filesystem::exists(filepath) || !std::filesystem::is_directory(filepath)) + return false; + + for (const auto& dir : std::filesystem::directory_iterator(filepath)) { + if (!dir.is_regular_file()) { + continue; + } + auto path = dir.path(); + + if (path.extension() != ".gci") + continue; + + FileIO file(path.string()); + + File fileData; + file.fileRead((void*)&fileData, sizeof(File), 0); + fileData.swapEndian(); + + m_files.push_back({fileData, file.fileSize(), path.filename().string(), false}); + } + + return true; +} + +void CardGciFolder::close() { + m_files.clear(); + m_folderPath = ""; + m_bat = BlockAllocationTable(); +} + +std::string_view CardGciFolder::cardFilename() const { return ""; } + +ECardResult CardGciFolder::getError() const { return ECardResult::READY; } + +ProbeResults CardGciFolder::probeCardFile(std::string_view filename) { + if (!std::filesystem::exists(filename) || !std::filesystem::is_directory(filename)) + return {ECardResult::NOCARD, 0, 0}; + + for (const auto& dir : std::filesystem::directory_iterator(filename)) { + if (!dir.is_regular_file()) { + continue; + } + auto path = dir.path(); + + if (path.extension() != ".gci") + continue; + + FileIO file(path.string()); + + File fileData; + file.fileRead(&fileData, sizeof(File), 0); + fileData.swapEndian(); + + bool isGameSame = memcmp(fileData.m_game, m_game, std::size(fileData.m_game)) == 0; + bool isMakerSame = memcmp(fileData.m_maker, m_maker, std::size(fileData.m_maker)) == 0; + + if (isGameSame && isMakerSame) + return {ECardResult::READY, static_cast(file.fileSize()), BlockSize}; + } + + return {ECardResult::NOFILE, 0, 0}; +} + +} // namespace aurora::card diff --git a/lib/card/CardGciFolder.hpp b/lib/card/CardGciFolder.hpp new file mode 100644 index 0000000..600deeb --- /dev/null +++ b/lib/card/CardGciFolder.hpp @@ -0,0 +1,77 @@ +#pragma once +#include +#include + +#include "BlockAllocationTable.hpp" +#include "CommonData.h" +#include "File.hpp" +#include "ICard.hpp" + +namespace aurora::card { + +class CardGciFolder : public ICard { +private: + struct GciFile { + File file; + size_t fileSize; + std::string filename; + bool opened = false; + }; + + std::vector m_files; + std::filesystem::path m_folderPath; + BlockAllocationTable m_bat; + + EEncoding m_encoding = EEncoding::ASCII; + + char m_game[5] = {'\0'}; + char m_maker[3] = {'\0'}; + + GciFile* getFile(FileHandle& fh); + const GciFile* getFile(FileHandle& fh) const; + GciFile* getFile(uint32_t idx); + const GciFile* getFile(uint32_t idx) const; +public: + CardGciFolder(); + ~CardGciFolder() override = default; + + CardGciFolder(const CardGciFolder& other) = delete; + CardGciFolder& operator=(const CardGciFolder& other) = delete; + CardGciFolder(CardGciFolder&& other); + CardGciFolder& operator=(CardGciFolder&& other); + + void InitCard(const char* game, const char* maker) override; + + ECardResult openFile(const char* filename, FileHandle& handleOut) override; + ECardResult openFile(uint32_t fileno, FileHandle& handleOut) override; + ECardResult createFile(const char* filename, size_t size, FileHandle& handleOut) override; + ECardResult closeFile(FileHandle& fh) override; + void deleteFile(const FileHandle& fh) override; + ECardResult deleteFile(const char* filename) override; + ECardResult deleteFile(uint32_t fileno) override; + ECardResult renameFile(const char* oldName, const char* newName) override; + ECardResult fileWrite(FileHandle& fh, const void* buf, size_t size) override; + ECardResult fileRead(FileHandle& fh, void* dst, size_t size) override; + void seek(FileHandle& fh, int32_t pos, SeekOrigin whence) override; + ECardResult getStatus(const FileHandle& fh, CardStat& statOut) const override; + ECardResult getStatus(uint32_t fileNo, CardStat& statOut) const override; + ECardResult setStatus(const FileHandle& fh, const CardStat& stat) override; + ECardResult setStatus(uint32_t fileNo, const CardStat& stat) override; + void setCurrentGame(const char* game) override; + const uint8_t* getCurrentGame() const override; + void setCurrentMaker(const char* maker) override; + const uint8_t* getCurrentMaker() const override; + void getSerial(uint64_t& serial) override; + void getChecksum(uint16_t& checksum, uint16_t& inverse) const override; + void getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) const override; + void getEncoding(uint16_t& encoding) const override; + void format(ECardSlot deviceId, ECardSize size = ECardSize::Card2043Mb, EEncoding encoding = EEncoding::ASCII) override; + void commit() override; + bool open(std::string_view filepath) override; + void close() override; + std::string_view cardFilename() const override; + ECardResult getError() const override; + ProbeResults probeCardFile(std::string_view filename) override; +}; + +} diff --git a/lib/card/Card.cpp b/lib/card/CardRawFile.cpp similarity index 85% rename from lib/card/Card.cpp rename to lib/card/CardRawFile.cpp index 0d9a370..63c2534 100644 --- a/lib/card/Card.cpp +++ b/lib/card/CardRawFile.cpp @@ -1,4 +1,4 @@ -#include "Card.hpp" +#include "CardRawFile.hpp" #include #include @@ -11,11 +11,9 @@ namespace aurora::card { -#define ROUND_UP_8192(val) (((val) + 8191) & ~8191) - static void NullFileAccess() { fprintf(stderr, "Attempted to access null file\n"); } -void Card::CardHeader::_swapEndian() { +void CardRawFile::CardHeader::_swapEndian() { m_formatTime = bswap(m_formatTime); m_sramBias = bswap(m_sramBias); m_sramLanguage = bswap(m_sramLanguage); @@ -28,9 +26,9 @@ void Card::CardHeader::_swapEndian() { m_checksumInv = bswap(m_checksumInv); } -Card::Card() { m_ch.raw.fill(0xFF); } +CardRawFile::CardRawFile() { m_ch.raw.fill(0xFF); } -Card::Card(Card&& other) { +CardRawFile::CardRawFile(CardRawFile&& other) { m_ch.raw = other.m_ch.raw; m_filename = std::move(other.m_filename); m_fileHandle = std::move(other.m_fileHandle); @@ -44,7 +42,7 @@ Card::Card(Card&& other) { std::copy(std::cbegin(other.m_maker), std::cend(other.m_maker), std::begin(m_maker)); } -Card& Card::operator=(Card&& other) { +CardRawFile& CardRawFile::operator=(CardRawFile&& other) { close(); m_ch.raw = other.m_ch.raw; @@ -62,7 +60,7 @@ Card& Card::operator=(Card&& other) { return *this; } -ECardResult Card::_pumpOpen() { +ECardResult CardRawFile::_pumpOpen() { if (m_opened) return ECardResult::READY; @@ -102,7 +100,7 @@ ECardResult Card::_pumpOpen() { return ECardResult::READY; } -Card::Card(const char* game, const char* maker) { +void CardRawFile::InitCard(const char* game, const char* maker) { m_ch.raw.fill(0xFF); if (game != nullptr && std::strlen(game) == 4) { @@ -113,9 +111,9 @@ Card::Card(const char* game, const char* maker) { } } -Card::~Card() { close(); } +CardRawFile::~CardRawFile() { CardRawFile::close(); } -ECardResult Card::openFile(const char* filename, FileHandle& handleOut) { +ECardResult CardRawFile::openFile(const char* filename, FileHandle& handleOut) { const ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) { return openRes; @@ -136,7 +134,7 @@ ECardResult Card::openFile(const char* filename, FileHandle& handleOut) { return ECardResult::FATAL_ERROR; } -ECardResult Card::openFile(uint32_t fileno, FileHandle& handleOut) { +ECardResult CardRawFile::openFile(uint32_t fileno, FileHandle& handleOut) { ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) return openRes; @@ -149,7 +147,7 @@ ECardResult Card::openFile(uint32_t fileno, FileHandle& handleOut) { return ECardResult::READY; } -void Card::_updateDirAndBat(const Directory& dir, const BlockAllocationTable& bat) { +void CardRawFile::_updateDirAndBat(const Directory& dir, const BlockAllocationTable& bat) { m_currentDir = !m_currentDir; Directory& updateDir = m_dirs[m_currentDir]; updateDir = dir; @@ -165,13 +163,13 @@ void Card::_updateDirAndBat(const Directory& dir, const BlockAllocationTable& ba m_dirty = true; } -void Card::_updateChecksum() { +void CardRawFile::_updateChecksum() { m_ch._swapEndian(); calculateChecksumBE(reinterpret_cast(m_ch.raw.data()), 0xFE, &m_ch.m_checksum, &m_ch.m_checksumInv); m_ch._swapEndian(); } -File* Card::_fileFromHandle(const FileHandle& fh) const { +File* CardRawFile::_fileFromHandle(const FileHandle& fh) const { if (!fh) { NullFileAccess(); return nullptr; @@ -179,7 +177,7 @@ File* Card::_fileFromHandle(const FileHandle& fh) const { return const_cast(m_dirs[m_currentDir]).getFile(fh.idx); } -ECardResult Card::createFile(const char* filename, size_t size, FileHandle& handleOut) { +ECardResult CardRawFile::createFile(const char* filename, size_t size, FileHandle& handleOut) { ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) return openRes; @@ -215,12 +213,12 @@ ECardResult Card::createFile(const char* filename, size_t size, FileHandle& hand return ECardResult::FATAL_ERROR; } -ECardResult Card::closeFile(FileHandle& fh) { +ECardResult CardRawFile::closeFile(FileHandle& fh) { fh.offset = 0; return ECardResult::READY; } -FileHandle Card::firstFile() { +FileHandle CardRawFile::firstFile() { const File* const f = m_dirs[m_currentDir].getFirstNonFreeFile(0, m_game, m_maker); if (f == nullptr) { @@ -230,7 +228,7 @@ FileHandle Card::firstFile() { return FileHandle(m_dirs[m_currentDir].indexForFile(f)); } -FileHandle Card::nextFile(const FileHandle& cur) { +FileHandle CardRawFile::nextFile(const FileHandle& cur) { if (!cur) { NullFileAccess(); return {}; @@ -244,7 +242,7 @@ FileHandle Card::nextFile(const FileHandle& cur) { return FileHandle(m_dirs[m_currentDir].indexForFile(next)); } -const char* Card::getFilename(const FileHandle& fh) const { +const char* CardRawFile::getFilename(const FileHandle& fh) const { const File* const f = _fileFromHandle(fh); if (f == nullptr) { @@ -254,7 +252,7 @@ const char* Card::getFilename(const FileHandle& fh) const { return f->m_filename; } -void Card::_deleteFile(File& f, BlockAllocationTable& bat) { +void CardRawFile::_deleteFile(File& f, BlockAllocationTable& bat) { uint16_t block = f.m_firstBlock; while (block != 0xFFFF) { /* TODO: add a fragmentation check */ @@ -265,7 +263,7 @@ void Card::_deleteFile(File& f, BlockAllocationTable& bat) { f = File(); } -void Card::deleteFile(const FileHandle& fh) { +void CardRawFile::deleteFile(const FileHandle& fh) { if (!fh) { NullFileAccess(); return; @@ -276,7 +274,7 @@ void Card::deleteFile(const FileHandle& fh) { _updateDirAndBat(dir, bat); } -ECardResult Card::deleteFile(const char* filename) { +ECardResult CardRawFile::deleteFile(const char* filename) { ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) return openRes; @@ -292,7 +290,7 @@ ECardResult Card::deleteFile(const char* filename) { return ECardResult::READY; } -ECardResult Card::deleteFile(uint32_t fileno) { +ECardResult CardRawFile::deleteFile(uint32_t fileno) { ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) return openRes; @@ -308,7 +306,7 @@ ECardResult Card::deleteFile(uint32_t fileno) { return ECardResult::READY; } -ECardResult Card::renameFile(const char* oldName, const char* newName) { +ECardResult CardRawFile::renameFile(const char* oldName, const char* newName) { const ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) { return openRes; @@ -338,7 +336,7 @@ ECardResult Card::renameFile(const char* oldName, const char* newName) { return ECardResult::READY; } -ECardResult Card::asyncWrite(FileHandle& fh, const void* buf, size_t size) { +ECardResult CardRawFile::fileWrite(FileHandle& fh, const void* buf, size_t size) { ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) return openRes; @@ -387,7 +385,7 @@ ECardResult Card::asyncWrite(FileHandle& fh, const void* buf, size_t size) { return ECardResult::READY; } -ECardResult Card::asyncRead(FileHandle& fh, void* dst, size_t size) { +ECardResult CardRawFile::fileRead(FileHandle& fh, void* dst, size_t size) { ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) return openRes; @@ -435,7 +433,7 @@ ECardResult Card::asyncRead(FileHandle& fh, void* dst, size_t size) { return ECardResult::READY; } -void Card::seek(FileHandle& fh, int32_t pos, SeekOrigin whence) { +void CardRawFile::seek(FileHandle& fh, int32_t pos, SeekOrigin whence) { if (!fh) { NullFileAccess(); return; @@ -457,9 +455,9 @@ void Card::seek(FileHandle& fh, int32_t pos, SeekOrigin whence) { } } -int32_t Card::tell(const FileHandle& fh) const { return fh.offset; } +int32_t CardRawFile::tell(const FileHandle& fh) const { return fh.offset; } -void Card::setPublic(const FileHandle& fh, bool pub) { +void CardRawFile::setPublic(const FileHandle& fh, bool pub) { File* file = _fileFromHandle(fh); if (!file) return; @@ -470,7 +468,7 @@ void Card::setPublic(const FileHandle& fh, bool pub) { file->m_permissions &= ~EPermissions::Public; } -bool Card::isPublic(const FileHandle& fh) const { +bool CardRawFile::isPublic(const FileHandle& fh) const { File* file = _fileFromHandle(fh); if (!file) return false; @@ -478,7 +476,7 @@ bool Card::isPublic(const FileHandle& fh) const { return static_cast(file->m_permissions & EPermissions::Public); } -void Card::setCanCopy(const FileHandle& fh, bool copy) const { +void CardRawFile::setCanCopy(const FileHandle& fh, bool copy) const { File* file = _fileFromHandle(fh); if (!file) return; @@ -489,7 +487,7 @@ void Card::setCanCopy(const FileHandle& fh, bool copy) const { file->m_permissions |= EPermissions::NoCopy; } -bool Card::canCopy(const FileHandle& fh) const { +bool CardRawFile::canCopy(const FileHandle& fh) const { File* file = _fileFromHandle(fh); if (!file) return false; @@ -497,7 +495,7 @@ bool Card::canCopy(const FileHandle& fh) const { return !static_cast(file->m_permissions & EPermissions::NoCopy); } -void Card::setCanMove(const FileHandle& fh, bool move) { +void CardRawFile::setCanMove(const FileHandle& fh, bool move) { File* file = _fileFromHandle(fh); if (!file) return; @@ -508,7 +506,7 @@ void Card::setCanMove(const FileHandle& fh, bool move) { file->m_permissions |= EPermissions::NoMove; } -bool Card::canMove(const FileHandle& fh) const { +bool CardRawFile::canMove(const FileHandle& fh) const { File* file = _fileFromHandle(fh); if (!file) return false; @@ -516,42 +514,7 @@ bool Card::canMove(const FileHandle& fh) const { return !static_cast(file->m_permissions & EPermissions::NoMove); } -static uint32_t BannerSize(EImageFormat fmt) { - switch (fmt) { - default: - case EImageFormat::None: - return 0; - case EImageFormat::C8: - return 3584; - case EImageFormat::RGB5A3: - return 6144; - } -} - -static uint32_t IconSize(EImageFormat fmt) { - switch (fmt) { - default: - case EImageFormat::None: - return 0; - case EImageFormat::C8: - return 1024; - case EImageFormat::RGB5A3: - return 2048; - } -} - -static uint32_t TlutSize(EImageFormat fmt) { - switch (fmt) { - default: - case EImageFormat::None: - case EImageFormat::RGB5A3: - return 0; - case EImageFormat::C8: - return 512; - } -} - -ECardResult Card::getStatus(const FileHandle& fh, CardStat& statOut) const { +ECardResult CardRawFile::getStatus(const FileHandle& fh, CardStat& statOut) const { if (!fh) { NullFileAccess(); return ECardResult::NOFILE; @@ -559,8 +522,8 @@ ECardResult Card::getStatus(const FileHandle& fh, CardStat& statOut) const { return getStatus(fh.idx, statOut); } -ECardResult Card::getStatus(uint32_t fileNo, CardStat& statOut) const { - ECardResult openRes = const_cast(this)->_pumpOpen(); +ECardResult CardRawFile::getStatus(uint32_t fileNo, CardStat& statOut) const { + ECardResult openRes = const_cast(this)->_pumpOpen(); if (openRes != ECardResult::READY) return openRes; @@ -612,7 +575,7 @@ ECardResult Card::getStatus(uint32_t fileNo, CardStat& statOut) const { return ECardResult::READY; } -ECardResult Card::setStatus(const FileHandle& fh, const CardStat& stat) { +ECardResult CardRawFile::setStatus(const FileHandle& fh, const CardStat& stat) { if (!fh) { NullFileAccess(); return ECardResult::NOFILE; @@ -620,7 +583,7 @@ ECardResult Card::setStatus(const FileHandle& fh, const CardStat& stat) { return setStatus(fh.idx, stat); } -ECardResult Card::setStatus(uint32_t fileNo, const CardStat& stat) { +ECardResult CardRawFile::setStatus(uint32_t fileNo, const CardStat& stat) { ECardResult openRes = _pumpOpen(); if (openRes != ECardResult::READY) return openRes; @@ -701,7 +664,7 @@ bool Card::moveFileTo(FileHandle& fh, Card& dest) } #endif -void Card::setCurrentGame(const char* game) { +void CardRawFile::setCurrentGame(const char* game) { if (game == nullptr) { std::memset(m_game, 0, sizeof(m_game)); return; @@ -715,7 +678,7 @@ void Card::setCurrentGame(const char* game) { std::memcpy(m_game, game, copy_amount); } -const uint8_t* Card::getCurrentGame() const { +const uint8_t* CardRawFile::getCurrentGame() const { if (std::strlen(m_game) == sizeof(m_game) - 1) { return reinterpret_cast(m_game); } @@ -723,7 +686,7 @@ const uint8_t* Card::getCurrentGame() const { return nullptr; } -void Card::setCurrentMaker(const char* maker) { +void CardRawFile::setCurrentMaker(const char* maker) { if (maker == nullptr) { std::memset(m_maker, 0, sizeof(m_maker)); return; @@ -737,7 +700,7 @@ void Card::setCurrentMaker(const char* maker) { std::memcpy(m_maker, maker, copy_amount); } -const uint8_t* Card::getCurrentMaker() const { +const uint8_t* CardRawFile::getCurrentMaker() const { if (std::strlen(m_maker) == sizeof(m_maker) - 1) { return reinterpret_cast(m_maker); } @@ -745,7 +708,7 @@ const uint8_t* Card::getCurrentMaker() const { return nullptr; } -void Card::getSerial(uint64_t& serial) { +void CardRawFile::getSerial(uint64_t& serial) { m_ch._swapEndian(); std::array serialBuf{}; @@ -758,19 +721,19 @@ void Card::getSerial(uint64_t& serial) { m_ch._swapEndian(); } -void Card::getChecksum(uint16_t& checksum, uint16_t& inverse) const { +void CardRawFile::getChecksum(uint16_t& checksum, uint16_t& inverse) const { checksum = m_ch.m_checksum; inverse = m_ch.m_checksumInv; } -void Card::getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) const { +void CardRawFile::getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) const { bytesNotUsed = m_bats[m_currentBat].numFreeBlocks() * 0x2000; filesNotUsed = m_dirs[m_currentDir].numFreeFiles(); } -void Card::getEncoding(uint16_t& encoding) const { encoding = m_ch.m_encoding; } +void CardRawFile::getEncoding(uint16_t& encoding) const { encoding = m_ch.m_encoding; } -void Card::format(ECardSlot id, ECardSize size, EEncoding encoding) { +void CardRawFile::format(ECardSlot id, ECardSize size, EEncoding encoding) { m_ch.raw.fill(0xFF); uint64_t rand = static_cast(getGCTime()); @@ -827,7 +790,7 @@ void Card::format(ECardSlot id, ECardSize size, EEncoding encoding) { } } -ProbeResults Card::probeCardFile(std::string_view filename) { +ProbeResults CardRawFile::probeCardFile(std::string_view filename) { std::filesystem::path path(filename); if (!std::filesystem::exists(path)) return {ECardResult::NOCARD, 0, 0}; @@ -835,7 +798,7 @@ ProbeResults Card::probeCardFile(std::string_view filename) { BlockSize}; } -void Card::commit() { +void CardRawFile::commit() { if (!m_dirty) return; if (m_fileHandle) { @@ -862,7 +825,7 @@ void Card::commit() { } } -bool Card::open(std::string_view filepath) { +bool CardRawFile::open(std::string_view filepath) { m_opened = false; m_filename = filepath; m_fileHandle = FileIO(m_filename); @@ -882,7 +845,7 @@ bool Card::open(std::string_view filepath) { return false; } -void Card::close() { +void CardRawFile::close() { m_opened = false; if (m_fileHandle) { commit(); @@ -890,19 +853,19 @@ void Card::close() { } } -ECardResult Card::getError() const { +ECardResult CardRawFile::getError() const { if (!m_fileHandle) return ECardResult::NOCARD; - ECardResult openRes = const_cast(this)->_pumpOpen(); + ECardResult openRes = const_cast(this)->_pumpOpen(); if (openRes != ECardResult::READY) return openRes; uint16_t ckSum, ckSumInv; - const_cast(*this).m_ch._swapEndian(); + const_cast(*this).m_ch._swapEndian(); calculateChecksumBE(reinterpret_cast(m_ch.raw.data()), 0xFE, &ckSum, &ckSumInv); bool res = ckSum == m_ch.m_checksum && ckSumInv == m_ch.m_checksumInv; - const_cast(*this).m_ch._swapEndian(); + const_cast(*this).m_ch._swapEndian(); if (!res) return ECardResult::BROKEN; diff --git a/lib/card/Card.hpp b/lib/card/CardRawFile.hpp similarity index 71% rename from lib/card/Card.hpp rename to lib/card/CardRawFile.hpp index fc33b00..5b21236 100644 --- a/lib/card/Card.hpp +++ b/lib/card/CardRawFile.hpp @@ -10,75 +10,12 @@ #include "File.hpp" #include "Util.hpp" -#define CARD_FILENAME_MAX 32 -#define CARD_ICON_MAX 8 +#include "CommonData.h" +#include "ICard.hpp" namespace aurora::card { -class FileHandle { - friend class Card; - uint32_t idx = UINT32_MAX; - int32_t offset = 0; - explicit FileHandle(uint32_t idx) : idx(idx) {} - -public: - FileHandle() = default; - explicit FileHandle(uint32_t idx, int32_t offset) : idx(idx), offset(offset) {} - - uint32_t getFileNo() const { return idx; } - uint32_t getOffset() const { return offset; } - explicit operator bool() const { return getFileNo() != UINT32_MAX; } -}; - -struct ProbeResults { - ECardResult x0_error; - uint32_t x4_cardSize; /* in megabits */ - uint32_t x8_sectorSize; /* in bytes */ -}; - -struct CardStat { - /* read-only (Set by Card::getStatus) */ - char x0_fileName[CARD_FILENAME_MAX]; - uint32_t x20_length; - uint32_t x24_time; /* seconds since 01/01/2000 midnight */ - std::array x28_gameName; - std::array x2c_company; - - /* read/write (Set by Card::getStatus/Card::setStatus) */ - 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 Card::getStatus) */ - uint32_t x3c_offsetBanner; - uint32_t x40_offsetBannerTlut; - std::array x44_offsetIcon; - 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 { +class CardRawFile : public ICard { #pragma pack(push, 4) struct CardHeader { union { @@ -128,11 +65,11 @@ class Card { ECardResult _pumpOpen(); public: - Card(); - Card(const Card& other) = delete; - Card& operator=(const Card& other) = delete; - Card(Card&& other); - Card& operator=(Card&& other); + CardRawFile(); + CardRawFile(const CardRawFile& other) = delete; + CardRawFile& operator=(const CardRawFile& other) = delete; + CardRawFile(CardRawFile&& other); + CardRawFile& operator=(CardRawFile&& other); /** * @brief Card @@ -140,8 +77,9 @@ public: * @param game The game code. * @param maker The maker code. */ - explicit Card(const char* game = nullptr, const char* maker = nullptr); - ~Card(); + void InitCard(const char* game = nullptr, const char* maker = nullptr) override; + + ~CardRawFile() override; /** * @brief openFile @@ -151,7 +89,7 @@ public: * * @return A result indicating the error status of the operation. */ - ECardResult openFile(const char* filename, FileHandle& handleOut); + ECardResult openFile(const char* filename, FileHandle& handleOut) override; /** * @brief openFile @@ -161,7 +99,7 @@ public: * * @return A result indicating the error status of the operation. */ - ECardResult openFile(uint32_t fileno, FileHandle& handleOut); + ECardResult openFile(uint32_t fileno, FileHandle& handleOut) override; /** * @brief createFile @@ -171,7 +109,7 @@ public: * * @return A result indicating the error status of the operation. */ - ECardResult createFile(const char* filename, size_t size, FileHandle& handleOut); + ECardResult createFile(const char* filename, size_t size, FileHandle& handleOut) override; /** * @brief closeFile @@ -180,7 +118,7 @@ public: * * @return READY */ - ECardResult closeFile(FileHandle& fh); + ECardResult closeFile(FileHandle& fh) override; /** * @brief firstFile @@ -215,21 +153,21 @@ public: * * @param fh File handle to delete. */ - void deleteFile(const FileHandle& fh); + void deleteFile(const FileHandle& fh) override; /** * @brief deleteFile * * @param filename The name of the file to delete. */ - ECardResult deleteFile(const char* filename); + ECardResult deleteFile(const char* filename) override; /** * @brief deleteFile * * @param fileno The file number indicating the file to delete. */ - ECardResult deleteFile(uint32_t fileno); + ECardResult deleteFile(uint32_t fileno) override; /** * @brief renameFile @@ -237,7 +175,7 @@ public: * @param oldName The old name of the file. * @param newName The new name to assign to the file. */ - ECardResult renameFile(const char* oldName, const char* newName); + ECardResult renameFile(const char* oldName, const char* newName) override; /** * @brief write @@ -246,7 +184,7 @@ public: * @param buf The buffer to write to the file. * @param size The size of the given buffer. */ - ECardResult asyncWrite(FileHandle& fh, const void* buf, size_t size); + ECardResult fileWrite(FileHandle& fh, const void* buf, size_t size) override; /** * @brief read @@ -255,7 +193,7 @@ public: * @param dst A buffer to read data into. * @param size The size of the buffer to read into. */ - ECardResult asyncRead(FileHandle& fh, void* dst, size_t size); + ECardResult fileRead(FileHandle& fh, void* dst, size_t size) override; /** * @brief seek @@ -264,7 +202,7 @@ public: * @param pos The position to seek to. * @param whence The origin to seek relative to. */ - void seek(FileHandle& fh, int32_t pos, SeekOrigin whence); + void seek(FileHandle& fh, int32_t pos, SeekOrigin whence) override; /** * @brief Returns the current offset of the specified file. @@ -334,7 +272,7 @@ public: * * @return NOFILE or READY */ - ECardResult getStatus(const FileHandle& fh, CardStat& statOut) const; + ECardResult getStatus(const FileHandle& fh, CardStat& statOut) const override; /** * @brief getStatus @@ -344,7 +282,7 @@ public: * * @return NOFILE or READY */ - ECardResult getStatus(uint32_t fileNo, CardStat& statOut) const; + ECardResult getStatus(uint32_t fileNo, CardStat& statOut) const override; /** * @brief setStatus @@ -354,7 +292,7 @@ public: * * @return NOFILE or READY */ - ECardResult setStatus(const FileHandle& fh, const CardStat& stat); + ECardResult setStatus(const FileHandle& fh, const CardStat& stat) override; /** * @brief setStatus @@ -364,7 +302,7 @@ public: * * @return NOFILE or READY */ - ECardResult setStatus(uint32_t fileNo, const CardStat& stat); + ECardResult setStatus(uint32_t fileNo, const CardStat& stat) override; #if 0 // TODO: Async-friendly implementations /** @@ -373,7 +311,7 @@ public: * @param dest The destination Card instance * @return True if successful, false otherwise */ - bool copyFileTo(FileHandle& fh, Card& dest); + bool copyFileTo(FileHandle& fh, CardRawFile& dest); /** * @brief moveFileTo @@ -381,7 +319,7 @@ public: * @param dest * @return */ - bool moveFileTo(FileHandle& fh, Card& dest); + bool moveFileTo(FileHandle& fh, CardRawFile& dest); #endif /** @@ -391,14 +329,14 @@ public: * * @sa openFile */ - void setCurrentGame(const char* game); + void setCurrentGame(const char* game) override; /** * @brief Returns the currently selected game. * * @return The selected game, or nullptr */ - const uint8_t* getCurrentGame() const; + const uint8_t* getCurrentGame() const override; /** * @brief Sets the current maker, if not null any openFile requests will only return files that match this maker. @@ -407,21 +345,21 @@ public: * * @sa openFile */ - void setCurrentMaker(const char* maker); + void setCurrentMaker(const char* maker) override; /** * @brief Returns the currently selected maker. * * @return The selected maker, or nullptr. */ - const uint8_t* getCurrentMaker() const; + const uint8_t* getCurrentMaker() const override; /** * @brief Retrieves the format assigned serial. * * @param[out] serial Out reference that will contain the serial number. */ - void getSerial(uint64_t& serial); + void getSerial(uint64_t& serial) override; /** * @brief Retrieves the checksum values of the Card system header. @@ -429,7 +367,7 @@ public: * @param checksum The checksum of the system header. * @param inverse The inverse checksum of the system header. */ - void getChecksum(uint16_t& checksum, uint16_t& inverse) const; + void getChecksum(uint16_t& checksum, uint16_t& inverse) const override; /** * @brief Retrieves the available storage and directory space. @@ -437,14 +375,14 @@ public: * @param bytesNotUsed Number of free bytes out. * @param filesNotUsed Number of free files out. */ - void getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) const; + void getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) const override; /** * @brief Retrieves the current file's encoding. * * @param encoding File encoding out. */ - void getEncoding(uint16_t& encoding) const; + void getEncoding(uint16_t& encoding) const override; /** * @brief Formats the memory card and assigns a new serial. @@ -453,44 +391,44 @@ public: * @param size The desired size of the file @sa ECardSize. * @param encoding The desired encoding @sa EEncoding. */ - void format(ECardSlot deviceId, ECardSize size = ECardSize::Card2043Mb, EEncoding encoding = EEncoding::ASCII); + void format(ECardSlot deviceId, ECardSize size = ECardSize::Card2043Mb, EEncoding encoding = EEncoding::ASCII) override; /** * @brief Returns basic stats about a card image without opening a handle. * * @return ProbeResults structure. */ - static ProbeResults probeCardFile(std::string_view filename); + ProbeResults probeCardFile(std::string_view filename) override; /** * @brief Writes any changes to the Card instance immediately to disk.
* Note: Under normal circumstances there is no need to call this function. */ - void commit(); + void commit() override; /** * @brief Opens card image (does nothing if currently open path matches). */ - bool open(std::string_view filepath); + bool open(std::string_view filepath) override; /** * @brief Commits changes to disk and closes host file. */ - void close(); + void close() override; /** * @brief Access host filename of card. * * @return A view to the card's filename. */ - std::string_view cardFilename() const { return m_filename; } + std::string_view cardFilename() const override { return m_filename; } /** * @brief Gets card-scope error state. * * @return READY, BROKEN, or NOCARD */ - ECardResult getError() const; + ECardResult getError() const override; /** * @return Whether or not the card is within a ready state. diff --git a/lib/card/CommonData.h b/lib/card/CommonData.h new file mode 100644 index 0000000..9902713 --- /dev/null +++ b/lib/card/CommonData.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include "Util.hpp" +#include "Constants.hpp" + +namespace aurora::card { + +class FileHandle { + friend class CardRawFile; + friend class CardGciFolder; + uint32_t idx = UINT32_MAX; + int32_t offset = 0; + explicit FileHandle(uint32_t idx) : idx(idx) {} + +public: + FileHandle() = default; + explicit FileHandle(uint32_t idx, int32_t offset) : idx(idx), offset(offset) {} + + [[nodiscard]] uint32_t getFileNo() const { return idx; } + [[nodiscard]] uint32_t getOffset() const { return offset; } + explicit operator bool() const { return getFileNo() != UINT32_MAX; } +}; + +struct ProbeResults { + ECardResult x0_error; + uint32_t x4_cardSize; /* in megabits */ + uint32_t x8_sectorSize; /* in bytes */ +}; + +struct CardStat { + /* read-only (Set by Card::getStatus) */ + char x0_fileName[CARD_FILENAME_MAX]; + uint32_t x20_length; + uint32_t x24_time; /* seconds since 01/01/2000 midnight */ + std::array x28_gameName; + std::array x2c_company; + + /* read/write (Set by Card::getStatus/Card::setStatus) */ + 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 Card::getStatus) */ + uint32_t x3c_offsetBanner; + uint32_t x40_offsetBannerTlut; + std::array x44_offsetIcon; + 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; } +}; + +} \ No newline at end of file diff --git a/lib/card/Constants.hpp b/lib/card/Constants.hpp index 71e60ac..285637e 100644 --- a/lib/card/Constants.hpp +++ b/lib/card/Constants.hpp @@ -4,6 +4,9 @@ #include "Util.hpp" +#define CARD_FILENAME_MAX 32 +#define CARD_ICON_MAX 8 + namespace aurora::card { constexpr uint32_t BlockSize = 0x2000; constexpr uint32_t MaxFiles = 127; @@ -72,4 +75,40 @@ enum class EEncoding : uint16_t { ASCII, /**< Standard ASCII Encoding */ SJIS /**< SJIS Encoding for japanese */ }; + +constexpr uint32_t BannerSize(EImageFormat fmt) { + switch (fmt) { + default: + case EImageFormat::None: + return 0; + case EImageFormat::C8: + return 3584; + case EImageFormat::RGB5A3: + return 6144; + } +} + +constexpr uint32_t IconSize(EImageFormat fmt) { + switch (fmt) { + default: + case EImageFormat::None: + return 0; + case EImageFormat::C8: + return 1024; + case EImageFormat::RGB5A3: + return 2048; + } +} + +constexpr uint32_t TlutSize(EImageFormat fmt) { + switch (fmt) { + default: + case EImageFormat::None: + case EImageFormat::RGB5A3: + return 0; + case EImageFormat::C8: + return 512; + } +} + } // namespace aurora::card diff --git a/lib/card/Directory.hpp b/lib/card/Directory.hpp index a752523..bee9027 100644 --- a/lib/card/Directory.hpp +++ b/lib/card/Directory.hpp @@ -7,7 +7,7 @@ namespace aurora::card { class Directory { - friend class Card; + friend class CardRawFile; #pragma pack(push, 4) using RawData = std::array; diff --git a/lib/card/File.hpp b/lib/card/File.hpp index b83aaa2..7bb9b5d 100644 --- a/lib/card/File.hpp +++ b/lib/card/File.hpp @@ -9,7 +9,8 @@ namespace aurora::card { class File { friend class IFileHandle; friend class Directory; - friend class Card; + friend class CardRawFile; + friend class CardGciFolder; #pragma pack(push, 4) using RawData = std::array; union { diff --git a/lib/card/FileIO.cpp b/lib/card/FileIO.cpp index 9566b82..50b8585 100644 --- a/lib/card/FileIO.cpp +++ b/lib/card/FileIO.cpp @@ -1,6 +1,7 @@ #include "FileIO.hpp" #include +#include #include #include @@ -98,6 +99,23 @@ bool FileIO::fileWrite(const void* buf, size_t length, off_t offset) { return true; } +size_t FileIO::fileSize() const { + SDL_PathInfo info; + if (SDL_GetPathInfo(m_path.c_str(), &info)) { + return info.size; + } + return 0; +} + +bool FileIO::deleteFile() { + if (SDL_RemovePath(m_path.c_str())) { + m_ready = false; + m_path.clear(); + return true; + } + return false; +} + FileIO::operator bool() const { return isReady(); } } // namespace aurora::card diff --git a/lib/card/FileIO.hpp b/lib/card/FileIO.hpp index 8c788ae..c274105 100644 --- a/lib/card/FileIO.hpp +++ b/lib/card/FileIO.hpp @@ -26,6 +26,8 @@ public: static SDL_IOStream* fileOpen(const std::string& path, const char* mode) {return path.empty() ? nullptr : SDL_IOFromFile(path.c_str(), mode);} bool fileRead(void* buf, size_t length, off_t offset); bool fileWrite(const void* buf, size_t length, off_t offset); + size_t fileSize() const; + bool deleteFile(); explicit operator bool() const; }; diff --git a/lib/card/ICard.hpp b/lib/card/ICard.hpp new file mode 100644 index 0000000..777ae2a --- /dev/null +++ b/lib/card/ICard.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "Constants.hpp" +#include "Util.hpp" + +namespace aurora::card { +class ICard { +public: + virtual ~ICard() = default; + + virtual void InitCard(const char* game = nullptr, const char* maker = nullptr) = 0; + + virtual ECardResult openFile(const char* filename, FileHandle& handleOut) = 0; + virtual ECardResult openFile(uint32_t fileno, FileHandle& handleOut) = 0; + virtual ECardResult createFile(const char* filename, size_t size, FileHandle& handleOut) = 0; + virtual ECardResult closeFile(FileHandle& fh) = 0; + virtual void deleteFile(const FileHandle& fh) = 0; + virtual ECardResult deleteFile(const char* filename) = 0; + virtual ECardResult deleteFile(uint32_t fileno) = 0; + virtual ECardResult renameFile(const char* oldName, const char* newName) = 0; + virtual ECardResult fileWrite(FileHandle& fh, const void* buf, size_t size) = 0; + virtual ECardResult fileRead(FileHandle& fh, void* dst, size_t size) = 0; + virtual void seek(FileHandle& fh, int32_t pos, SeekOrigin whence) = 0; + virtual ECardResult getStatus(const FileHandle& fh, CardStat& statOut) const = 0; + virtual ECardResult getStatus(uint32_t fileNo, CardStat& statOut) const = 0; + virtual ECardResult setStatus(const FileHandle& fh, const CardStat& stat) = 0; + virtual ECardResult setStatus(uint32_t fileNo, const CardStat& stat) = 0; + virtual void setCurrentGame(const char* game) = 0; + virtual const uint8_t* getCurrentGame() const = 0; + virtual void setCurrentMaker(const char* maker) = 0; + virtual const uint8_t* getCurrentMaker() const = 0; + virtual void getSerial(uint64_t& serial) = 0; + virtual void getChecksum(uint16_t& checksum, uint16_t& inverse) const = 0; + virtual void getFreeBlocks(int32_t& bytesNotUsed, int32_t& filesNotUsed) const = 0; + virtual void getEncoding(uint16_t& encoding) const = 0; + virtual void format(ECardSlot deviceId, ECardSize size = ECardSize::Card2043Mb, EEncoding encoding = EEncoding::ASCII) = 0; + virtual void commit() = 0; + virtual bool open(std::string_view filepath) = 0; + virtual void close() = 0; + virtual std::string_view cardFilename() const = 0; + virtual ECardResult getError() const = 0; + virtual ProbeResults probeCardFile(std::string_view filename) = 0; +}; +} \ No newline at end of file diff --git a/lib/card/Util.hpp b/lib/card/Util.hpp index 1d1f5c1..0875d7e 100644 --- a/lib/card/Util.hpp +++ b/lib/card/Util.hpp @@ -38,6 +38,8 @@ } #endif +#define ROUND_UP_8192(val) (((val) + 8191) & ~8191) + namespace aurora::card { uint64_t getGCTime(); diff --git a/lib/dolphin/card.cpp b/lib/dolphin/card.cpp index 5f28e50..3428b09 100644 --- a/lib/dolphin/card.cpp +++ b/lib/dolphin/card.cpp @@ -4,27 +4,28 @@ #include "dolphin/types.h" -#include "../card/Card.hpp" +#include "../card/CardRawFile.hpp" #include "../card/DolphinCardPath.hpp" #include "../logging.hpp" +#include "../card/CardGciFolder.hpp" namespace { aurora::Module Log("aurora::card"); -std::array CardChannels = {{ - aurora::card::Card{nullptr, nullptr}, - aurora::card::Card{nullptr, nullptr}, -}}; +std::array, 2> CardChannels = {{}}; std::array cardPaths; constexpr uint16_t CARD_SECTOR_SIZE = 8192; -#define GET_CARD(slot) &CardChannels[slot] -#define CARD_READY(slot) std::filesystem::exists(CardChannels[slot].cardFilename()) -#define CARD_STUB Log.debug("{} is stubbed.", __FUNCTION__); #define CARD_REGION "USA" +#define GET_CARD(slot) CardChannels[slot] +#define CARD_READY(slot) (SelectedFileType == GciFolder ? true : std::filesystem::exists(CardChannels[slot]->cardFilename())) +#define CARD_STUB Log.debug("{} is stubbed.", __FUNCTION__); + +#define CARD_USE_GCI_FOLDER SelectedFileType == GciFolder + bool Initialized = false; -bool UseGciFolder = false; +CARDFileType SelectedFileType = CARDFileType::GciFolder; bool UseFastMode = false; void CopyKabuFileHandleToDolphin(const s32 chan, const aurora::card::FileHandle& handle, CARDFileInfo* fileInfo) { @@ -41,11 +42,12 @@ std::string GetCardFullPath(const std::string& path, const aurora::card::ECardSl if (path.empty()) return ""; - if (UseGciFolder) { - // TODO: probably not this - return fmt::format("{}/{}/Card {}", path, CARD_REGION, slot == aurora::card::ECardSlot::SlotA ? "A" : "B"); + std::filesystem::path filePath(path); + + if (CARD_USE_GCI_FOLDER) { + return (filePath / CARD_REGION / (slot == aurora::card::ECardSlot::SlotA ? "Card A" : "Card B")).string(); } else { - return fmt::format("{}/MemoryCard{}.{}.raw", path, slot == aurora::card::ECardSlot::SlotA ? "A" : "B", CARD_REGION); + return (filePath / fmt::format("MemoryCard{}.{}.raw", slot == aurora::card::ECardSlot::SlotA ? "A" : "B", CARD_REGION)).string(); } } } // namespace @@ -97,15 +99,15 @@ void CARDDetectDolphin(const s32 chan) { if (chan == 0 || chan == 1) { cardPaths[chan] = - aurora::card::ResolveDolphinCardPath(static_cast(chan), CARD_REGION, UseGciFolder); + aurora::card::ResolveDolphinCardPath(static_cast(chan), CARD_REGION, CARD_USE_GCI_FOLDER); if (cardPaths[chan].empty()) { Log.error("Failed to detect Dolphin Card!"); return; } Log.info("Detected Dolphin Card at: {}", cardPaths[chan]); } else { - cardPaths[0] = aurora::card::ResolveDolphinCardPath(aurora::card::ECardSlot::SlotA, CARD_REGION, UseGciFolder); - cardPaths[1] = aurora::card::ResolveDolphinCardPath(aurora::card::ECardSlot::SlotB, CARD_REGION, UseGciFolder); + cardPaths[0] = aurora::card::ResolveDolphinCardPath(aurora::card::ECardSlot::SlotA, CARD_REGION, CARD_USE_GCI_FOLDER); + cardPaths[1] = aurora::card::ResolveDolphinCardPath(aurora::card::ECardSlot::SlotB, CARD_REGION, CARD_USE_GCI_FOLDER); if (cardPaths[0].empty() && cardPaths[1].empty()) { Log.error("Failed to detect Dolphin Card!"); @@ -136,6 +138,10 @@ void CARDSetBasePath(const char* path, const s32 chan) { } } +void CARDSetLoadType(CARDFileType type) { + SelectedFileType = type; +} + void CARDInit(const char* game, const char* maker) { if (Initialized) { return; @@ -143,8 +149,17 @@ void CARDInit(const char* game, const char* maker) { Initialized = true; Log.info("CARD API Initialized BUILT <{} {}>", __DATE__, __TIME__); - CardChannels[0] = aurora::card::Card{game, maker}; - CardChannels[1] = aurora::card::Card{game, maker}; + for (int i = 0; i < CardChannels.size(); ++i) { + switch (SelectedFileType) { + case RawImage: + CardChannels[i] = std::make_unique(); + break; + case GciFolder: + CardChannels[i] = std::make_unique(); + break; + } + CardChannels[i]->InitCard(game, maker); + } std::string cardWorkingDir; if (aurora::g_config.configPath != nullptr) @@ -163,7 +178,7 @@ void CARDInit(const char* game, const char* maker) { const auto& curPath = cardPaths[i]; if (std::filesystem::exists(curPath)) { - CardChannels[i].open(curPath); + CardChannels[i]->open(curPath); loadedCard = true; Log.info("Loaded GC Card Image: {}", curPath); } @@ -171,10 +186,10 @@ void CARDInit(const char* game, const char* maker) { // create a SlotA card if no cards were loaded if (!loadedCard) { - CardChannels[0].open(cardPaths[0]); - CardChannels[0].format(aurora::card::ECardSlot::SlotA); - CardChannels[0].close(); - CardChannels[0].open(cardPaths[0]); + CardChannels[0]->open(cardPaths[0]); + CardChannels[0]->format(aurora::card::ECardSlot::SlotA); + CardChannels[0]->close(); + CardChannels[0]->open(cardPaths[0]); } } @@ -183,8 +198,8 @@ void CARDSetGameAndMaker(const s32 chan, const char* game, const char* maker) { return; } - CardChannels[chan].setCurrentGame(game); - CardChannels[chan].setCurrentMaker(maker); + CardChannels[chan]->setCurrentGame(game); + CardChannels[chan]->setCurrentMaker(maker); } // TODO: Investigate if this is necessary. Do some games throw an error if CARD's fast mode can't be use? @@ -203,7 +218,8 @@ s32 CARDCheck(const s32 chan) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); + return static_cast(card->getError()); } @@ -214,7 +230,7 @@ s32 CARDCheckAsync(const s32 chan, const CARDCallback callback) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); const auto res = static_cast(card->getError()); callback(chan, res); return static_cast(card->getError()); @@ -227,7 +243,7 @@ s32 CARDCheckEx(const s32 chan, s32* xferBytes [[maybe_unused]]) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); return static_cast(card->getError()); } @@ -236,7 +252,7 @@ s32 CARDCheckExAsync(const s32 chan, s32* xferBytes [[maybe_unused]], const CARD if (chan < 0 || chan >= 2) { return CARD_RESULT_FATAL_ERROR; } - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); const auto res = static_cast(card->getError()); callback(chan, res); return static_cast(card->getError()); @@ -248,7 +264,7 @@ s32 CARDCreate(const s32 chan, const char* fileName, const u32 size, CARDFileInf } if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); aurora::card::FileHandle handle; auto res = card->createFile(fileName, size, handle); @@ -278,7 +294,7 @@ s32 CARDDelete(const s32 chan, const char* fileName) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); const auto res = card->deleteFile(fileName); if (res != aurora::card::ECardResult::READY) { @@ -306,7 +322,7 @@ s32 CARDFastDelete(const s32 chan, const s32 fileNo) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); const auto res = card->deleteFile(fileNo); if (res != aurora::card::ECardResult::READY) { Log.error("Failed to delete file at idx: {}", fileNo); @@ -333,7 +349,7 @@ s32 CARDFastOpen(const s32 chan, const s32 fileNo, CARDFileInfo* fileInfo) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); aurora::card::FileHandle handle; const auto res = card->openFile(fileNo, handle); @@ -352,7 +368,7 @@ s32 CARDFormat(s32 chan) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); card->format(static_cast(chan)); card->commit(); return CARD_RESULT_READY; @@ -374,7 +390,7 @@ s32 CARDFreeBlocks(const s32 chan, s32* byteNotUsed, s32* filesNotUsed) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); card->getFreeBlocks(*byteNotUsed, *filesNotUsed); return CARD_RESULT_READY; } @@ -395,7 +411,7 @@ s32 CARDGetEncoding(const s32 chan, u16* encode) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); card->getEncoding(*encode); return CARD_RESULT_READY; } @@ -416,7 +432,7 @@ s32 CARDGetResultCode(const s32 chan) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); return static_cast(card->getError()); } @@ -438,7 +454,7 @@ s32 CARDGetSerialNo(const s32 chan, u64* serialNo) { if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); card->getSerial(*serialNo); return CARD_RESULT_READY; } @@ -458,7 +474,7 @@ s32 CARDGetStatus(const s32 chan, s32 fileNo, CARDStat* stat) { } if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); aurora::card::CardStat kabuStat; const auto res = card->getStatus(fileNo, kabuStat); @@ -502,7 +518,7 @@ s32 CARDOpen(const s32 chan, const char* fileName, CARDFileInfo* fileInfo) { } if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); aurora::card::FileHandle handle; const auto res = card->openFile(fileName, handle); @@ -518,7 +534,9 @@ BOOL CARDProbe(const s32 chan) { if (chan < 0 || chan >= 2) { return CARD_RESULT_FATAL_ERROR; } - aurora::card::ProbeResults probeData = aurora::card::Card::probeCardFile(cardPaths[chan]); + const auto& card = GET_CARD(chan); + + aurora::card::ProbeResults probeData = card->probeCardFile(cardPaths[chan]); return probeData.x0_error != aurora::card::ECardResult::NOCARD ? TRUE : FALSE; } @@ -526,9 +544,10 @@ s32 CARDProbeEx(const s32 chan, s32* memSize, s32* sectorSize) { if (chan < 0 || chan >= 2) { return CARD_RESULT_FATAL_ERROR; } + const auto& card = GET_CARD(chan); // ReSharper disable once CppUseStructuredBinding - const aurora::card::ProbeResults probeData = aurora::card::Card::probeCardFile(cardPaths[chan]); + const aurora::card::ProbeResults probeData = card->probeCardFile(cardPaths[chan]); *memSize = probeData.x4_cardSize; *sectorSize = probeData.x8_sectorSize; @@ -541,7 +560,7 @@ s32 CARDRename(const s32 chan, const char* oldName, const char* newName) { } if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); return static_cast(card->renameFile(oldName, newName)); } @@ -580,7 +599,7 @@ s32 CARDSetStatus(const s32 chan, s32 fileNo, const CARDStat* stat) { } if (!CARD_READY(chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(chan); + const auto& card = GET_CARD(chan); aurora::card::CardStat kabuStat; CopyDolphinStatsToKabu(kabuStat, stat); @@ -639,7 +658,7 @@ s32 CARDClose(CARDFileInfo* fileInfo) { } if (!CARD_READY(fileInfo->chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(fileInfo->chan); + const auto& card = GET_CARD(fileInfo->chan); auto handle = CreateKabuFileHandleFromDolphin(fileInfo); const auto res = card->closeFile(handle); @@ -656,12 +675,12 @@ s32 CARDRead(const CARDFileInfo* fileInfo, void* addr, s32 length, const s32 off } if (!CARD_READY(fileInfo->chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(fileInfo->chan); + const auto& card = GET_CARD(fileInfo->chan); aurora::card::FileHandle handle = CreateKabuFileHandleFromDolphin(fileInfo); card->seek(handle, offset, aurora::card::SeekOrigin::Begin); - auto res = card->asyncRead(handle, addr, length); + auto res = card->fileRead(handle, addr, length); if (res != aurora::card::ECardResult::READY) Log.error("Failed to read {} bytes from card", length); @@ -682,12 +701,12 @@ s32 CARDWrite(const CARDFileInfo* fileInfo, const void* addr, const s32 length, } if (!CARD_READY(fileInfo->chan)) return CARD_RESULT_NOCARD; - const auto card = GET_CARD(fileInfo->chan); + const auto& card = GET_CARD(fileInfo->chan); aurora::card::FileHandle handle = CreateKabuFileHandleFromDolphin(fileInfo); card->seek(handle, offset, aurora::card::SeekOrigin::Begin); - auto res = card->asyncWrite(handle, addr, length); + auto res = card->fileWrite(handle, addr, length); if (res != aurora::card::ECardResult::READY) { Log.error("Failed to write {} bytes to card", length);