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
This commit is contained in:
CraftyBoss
2026-04-23 22:39:42 -07:00
committed by GitHub
parent 524b683fe0
commit a801671643
16 changed files with 844 additions and 250 deletions
+2 -1
View File
@@ -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
+11
View File
@@ -1,6 +1,7 @@
#ifndef _DOLPHIN_CARD_H_
#define _DOLPHIN_CARD_H_
#include <string_view>
#include <dolphin/os.h>
#include <dolphin/dsp.h>
#include <dolphin/dvd.h>
@@ -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);
+1 -1
View File
@@ -7,7 +7,7 @@
namespace aurora::card {
class BlockAllocationTable {
friend class Card;
friend class CardRawFile;
#pragma pack(push, 4)
union {
struct {
+405
View File
@@ -0,0 +1,405 @@
#include "CardGciFolder.hpp"
#include <filesystem>
#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<uint8_t> 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<uint32_t>(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<int>(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<const uint8_t*>(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<const uint8_t*>(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<uint32_t>(file.fileSize()), BlockSize};
}
return {ECardResult::NOFILE, 0, 0};
}
} // namespace aurora::card
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include <filesystem>
#include <vector>
#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<GciFile> 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;
};
}
+55 -92
View File
@@ -1,4 +1,4 @@
#include "Card.hpp"
#include "CardRawFile.hpp"
#include <algorithm>
#include <cstdio>
@@ -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<uint16_t*>(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<Directory&>(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<bool>(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<bool>(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<bool>(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<Card*>(this)->_pumpOpen();
ECardResult CardRawFile::getStatus(uint32_t fileNo, CardStat& statOut) const {
ECardResult openRes = const_cast<CardRawFile*>(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<const uint8_t*>(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<const uint8_t*>(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<uint32_t, 8> 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<uint64_t>(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<Card*>(this)->_pumpOpen();
ECardResult openRes = const_cast<CardRawFile*>(this)->_pumpOpen();
if (openRes != ECardResult::READY)
return openRes;
uint16_t ckSum, ckSumInv;
const_cast<Card&>(*this).m_ch._swapEndian();
const_cast<CardRawFile&>(*this).m_ch._swapEndian();
calculateChecksumBE(reinterpret_cast<const uint16_t*>(m_ch.raw.data()), 0xFE, &ckSum, &ckSumInv);
bool res = ckSum == m_ch.m_checksum && ckSumInv == m_ch.m_checksumInv;
const_cast<Card&>(*this).m_ch._swapEndian();
const_cast<CardRawFile&>(*this).m_ch._swapEndian();
if (!res)
return ECardResult::BROKEN;
+43 -105
View File
@@ -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<uint8_t, 4> x28_gameName;
std::array<uint8_t, 2> 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<uint32_t, CARD_ICON_MAX> 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. <br />
* <b>Note:</b> <i>Under normal circumstances there is no need to call this function.</i>
*/
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.
+74
View File
@@ -0,0 +1,74 @@
#pragma once
#include <array>
#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<uint8_t, 4> x28_gameName;
std::array<uint8_t, 2> 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<uint32_t, CARD_ICON_MAX> 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; }
};
}
+39
View File
@@ -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
+1 -1
View File
@@ -7,7 +7,7 @@
namespace aurora::card {
class Directory {
friend class Card;
friend class CardRawFile;
#pragma pack(push, 4)
using RawData = std::array<uint8_t, BlockSize>;
+2 -1
View File
@@ -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<uint8_t, 0x40>;
union {
+18
View File
@@ -1,6 +1,7 @@
#include "FileIO.hpp"
#include <SDL3/SDL_iostream.h>
#include <SDL3/SDL_filesystem.h>
#include <cstdint>
#include <utility>
@@ -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
+2
View File
@@ -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;
};
+44
View File
@@ -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;
};
}
+2
View File
@@ -38,6 +38,8 @@
}
#endif
#define ROUND_UP_8192(val) (((val) + 8191) & ~8191)
namespace aurora::card {
uint64_t getGCTime();
+68 -49
View File
@@ -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<aurora::card::Card, 2> CardChannels = {{
aurora::card::Card{nullptr, nullptr},
aurora::card::Card{nullptr, nullptr},
}};
std::array<std::unique_ptr<aurora::card::ICard>, 2> CardChannels = {{}};
std::array<std::string, 2> 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<aurora::card::ECardSlot>(chan), CARD_REGION, UseGciFolder);
aurora::card::ResolveDolphinCardPath(static_cast<aurora::card::ECardSlot>(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<aurora::card::CardRawFile>();
break;
case GciFolder:
CardChannels[i] = std::make_unique<aurora::card::CardGciFolder>();
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<s32>(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<s32>(card->getError());
callback(chan, res);
return static_cast<s32>(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<s32>(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<s32>(card->getError());
callback(chan, res);
return static_cast<s32>(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<aurora::card::ECardSlot>(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<s32>(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<s32>(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);