diff --git a/Makefile b/Makefile
index e343a58..2c9fcb2 100644
--- a/Makefile
+++ b/Makefile
@@ -25,7 +25,8 @@ VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_PATCH)
#-------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
-SOURCES := source
+SOURCES := source \
+ source/devoptab
DATA := data
INCLUDES := source \
include \
@@ -38,7 +39,7 @@ CFLAGS := -Wall -Werror -save-temps \
$(MACHDEP) \
$(BUILD_CFLAGS)
-CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__
+CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -fno-exceptions
CXXFLAGS := $(CFLAGS) -std=gnu++20
diff --git a/README.md b/README.md
index d81bdcb..06be463 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
[![Publish Docker Image](https://github.com/wiiu-env/libmocha/actions/workflows/push_image.yml/badge.svg)](https://github.com/wiiu-env/libmocha/actions/workflows/push_image.yml)
+**This library is still WIP, may not work as expected or have breaking changes in the near future**
+
# libmocha
Requires the [MochaPayload](https://github.com/wiiu-env/MochaPayload) to be running via [EnvironmentLoader](https://github.com/wiiu-env/EnvironmentLoader).
Requires [wut](https://github.com/devkitPro/wut) for building.
diff --git a/include/mocha/fsa.h b/include/mocha/fsa.h
index d5677bd..076c59f 100644
--- a/include/mocha/fsa.h
+++ b/include/mocha/fsa.h
@@ -24,7 +24,7 @@ FSError FSAEx_RawOpen(FSClient *client, const char *device_path, int32_t *outHan
* @param outHandle pointer where the handle of the raw device will be stored
* @return
*/
-FSError FSAEx_RawOpenEx(int clientHandle, const char *device_path, int32_t *outHandle);
+FSError FSAEx_RawOpenEx(FSAClientHandle clientHandle, const char *device_path, int32_t *outHandle);
/**
* Closes a devices that was previously opened via FSAEx_RawOpen
@@ -40,7 +40,7 @@ FSError FSAEx_RawClose(FSClient *client, int32_t device_handle);
* @param device_handle device handle
* @return
*/
-FSError FSAEx_RawCloseEx(int clientHandle, int32_t device_handle);
+FSError FSAEx_RawCloseEx(FSAClientHandle clientHandle, int32_t device_handle);
/**
* Read data from a device handle.
@@ -66,7 +66,7 @@ FSError FSAEx_RawRead(FSClient *client, void *data, uint32_t size_bytes, uint32_
* @param device_handle valid device handle.
* @return
*/
-FSError FSAEx_RawReadEx(int clientHandle, void *data, uint32_t size_bytes, uint32_t cnt, uint64_t blocks_offset, int device_handle);
+FSError FSAEx_RawReadEx(FSAClientHandle clientHandle, void *data, uint32_t size_bytes, uint32_t cnt, uint64_t blocks_offset, int device_handle);
/**
@@ -93,7 +93,7 @@ FSError FSAEx_RawWrite(FSClient *client, const void *data, uint32_t size_bytes,
* @param device_handle valid device handle.
* @return
*/
-FSError FSAEx_RawWriteEx(int clientHandle, const void *data, uint32_t size_bytes, uint32_t cnt, uint64_t blocks_offset, int device_handle);
+FSError FSAEx_RawWriteEx(FSAClientHandle clientHandle, const void *data, uint32_t size_bytes, uint32_t cnt, uint64_t blocks_offset, int device_handle);
#ifdef __cplusplus
} // extern "C"
diff --git a/include/mocha/mocha.h b/include/mocha/mocha.h
index 1dfe9b3..83d6a2b 100644
--- a/include/mocha/mocha.h
+++ b/include/mocha/mocha.h
@@ -13,6 +13,8 @@ typedef enum MochaUtilsStatus {
MOCHA_RESULT_INVALID_ARGUMENT = -0x01,
MOCHA_RESULT_MAX_CLIENT = -0x02,
MOCHA_RESULT_OUT_OF_MEMORY = -0x03,
+ MOCHA_RESULT_ALREADY_EXISTS = -0x04,
+ MOCHA_RESULT_ADD_DEVOPTAB_FAILED = -0x05,
MOCHA_RESULT_NOT_FOUND = -0x06,
MOCHA_RESULT_UNSUPPORTED_API_VERSION = -0x10,
MOCHA_RESULT_UNSUPPORTED_COMMAND = -0x11,
@@ -253,6 +255,33 @@ MochaUtilsStatus Mocha_ODMGetDiscKey(WUDDiscKey *discKey);
*/
MochaUtilsStatus Mocha_SEEPROMRead(uint8_t *out_buffer, uint32_t offset, uint32_t size);
+/**
+ * Mounts a device (dev_path) to a given path (mount_path) and make a accessible via the
+ * newlib devoptab (standard POSIX file I/O)
+ *
+ * Requires Mocha API Version: 1
+ * @param virt_name Name which should be used for the devoptab. When choosing e.g. "storage_usb" the mounted device can be accessed via "storage_usb:/".
+ * @param dev_path (optional) Cafe OS internal device path (e.g. /dev/slc01). If the given dev_path is NULL, an existing mount will be used (and is expected)
+ * @param mount_path Path where CafeOS should mount the device to. Must be globally unique and start with "/vol/storage_"
+ * @return MOCHA_RESULT_SUCCESS: The device has been mounted successfully
+ * MOCHA_RESULT_MAX_CLIENT: The maximum number of FSAClients have been registered.
+ * MOCHA_RESULT_LIB_UNINITIALIZED: Library was not initialized. Call Mocha_InitLibrary() before using this function.
+ * MOCHA_RESULT_UNSUPPORTED_COMMAND: Command not supported by the currently loaded mocha version.
+ * MOCHA_RESULT_UNKNOWN_ERROR: Failed to retrieve the environment path.
+ */
+MochaUtilsStatus Mocha_MountFS(const char *virt_name, const char *dev_path, const char *mount_path);
+
+MochaUtilsStatus Mocha_MountFSEx(const char *virt_name, const char *dev_path, const char *mount_path, FSAMountFlags mountFlags, void *mountArgBuf, int mountArgBufLen);
+
+/**
+ * Unmounts a mount by it's name.
+ * @param virt_name Name of the mount.
+ * @return MOCHA_RESULT_SUCCESS: The unmount was successful
+ * MOCHA_RESULT_INVALID_ARGUMENT:
+ * MOCHA_RESULT_NOT_FOUND: No mount with the given name has been found.
+ */
+MochaUtilsStatus Mocha_UnmountFS(const char *virt_name);
+
#ifdef __cplusplus
} // extern "C"
#endif
\ No newline at end of file
diff --git a/source/devoptab/FastLockWrapper.h b/source/devoptab/FastLockWrapper.h
new file mode 100644
index 0000000..46f3a98
--- /dev/null
+++ b/source/devoptab/FastLockWrapper.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include
+class FastLockWrapper {
+public:
+ FastLockWrapper() {
+ OSFastMutex_Init(&mutex, "generic lock");
+ }
+
+ void lock() {
+ OSFastMutex_Lock(&mutex);
+ }
+
+ void unlock() {
+ OSFastMutex_Unlock(&mutex);
+ }
+
+private:
+ OSFastMutex mutex{};
+};
diff --git a/source/devoptab/devoptab_fs.cpp b/source/devoptab/devoptab_fs.cpp
new file mode 100644
index 0000000..c8ceefb
--- /dev/null
+++ b/source/devoptab/devoptab_fs.cpp
@@ -0,0 +1,202 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include "mocha/mocha.h"
+#include
+#include
+#include
+
+static const devoptab_t fsa_default_devoptab = {
+ .structSize = sizeof(__fsa_file_t),
+ .open_r = __fsa_open,
+ .close_r = __fsa_close,
+ .write_r = __fsa_write,
+ .read_r = __fsa_read,
+ .seek_r = __fsa_seek,
+ .fstat_r = __fsa_fstat,
+ .stat_r = __fsa_stat,
+ .link_r = __fsa_link,
+ .unlink_r = __fsa_unlink,
+ .chdir_r = __fsa_chdir,
+ .rename_r = __fsa_rename,
+ .mkdir_r = __fsa_mkdir,
+ .dirStateSize = sizeof(__fsa_dir_t),
+ .diropen_r = __fsa_diropen,
+ .dirreset_r = __fsa_dirreset,
+ .dirnext_r = __fsa_dirnext,
+ .dirclose_r = __fsa_dirclose,
+ .statvfs_r = __fsa_statvfs,
+ .ftruncate_r = __fsa_ftruncate,
+ .fsync_r = __fsa_fsync,
+ .chmod_r = __fsa_chmod,
+ .fchmod_r = __fsa_fchmod,
+ .rmdir_r = __fsa_rmdir,
+ .lstat_r = __fsa_stat,
+ .utimes_r = __fsa_utimes,
+};
+
+static bool fsa_initialised = false;
+static FSADeviceData fsa_mounts[8];
+
+static void fsaResetMount(FSADeviceData *mount, uint32_t id) {
+ *mount = {};
+ memcpy(&mount->device, &fsa_default_devoptab, sizeof(fsa_default_devoptab));
+ mount->device.name = mount->name;
+ mount->device.deviceData = mount;
+ mount->id = id;
+ mount->setup = false;
+ mount->mounted = false;
+ mount->clientHandle = -1;
+ mount->deviceSizeInSectors = 0;
+ mount->deviceSectorSize = 0;
+ memset(mount->mount_path, 0, sizeof(mount->mount_path));
+ memset(mount->name, 0, sizeof(mount->name));
+ DCFlushRange(mount, sizeof(*mount));
+}
+
+void fsaInit() {
+ if (!fsa_initialised) {
+ uint32_t total = sizeof(fsa_mounts) / sizeof(fsa_mounts[0]);
+ for (uint32_t i = 0; i < total; i++) {
+ fsaResetMount(&fsa_mounts[i], i);
+ }
+ fsa_initialised = true;
+ }
+}
+
+std::mutex fsaMutex;
+
+FSADeviceData *fsa_alloc() {
+ uint32_t i;
+ uint32_t total = sizeof(fsa_mounts) / sizeof(fsa_mounts[0]);
+ FSADeviceData *mount;
+
+ fsaInit();
+
+ for (i = 0; i < total; i++) {
+ mount = &fsa_mounts[i];
+ if (!mount->setup) {
+ return mount;
+ }
+ }
+
+ return nullptr;
+}
+
+static void fsa_free(FSADeviceData *mount) {
+ FSError res;
+ if (mount->mounted) {
+ if ((res = FSAUnmount(mount->clientHandle, mount->mount_path, FSA_UNMOUNT_FLAG_FORCE)) < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAUnmount %s for %s failed: %s", mount->mount_path, mount->name, FSAGetStatusStr(res));
+ }
+ }
+ res = FSADelClient(mount->clientHandle);
+ if (res < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSADelClient for %s failed: %d", mount->name, FSAGetStatusStr(res));
+ }
+ fsaResetMount(mount, mount->id);
+}
+
+MochaUtilsStatus Mocha_UnmountFS(const char *virt_name) {
+ if (!virt_name) {
+ return MOCHA_RESULT_INVALID_ARGUMENT;
+ }
+ std::lock_guard lock(fsaMutex);
+ uint32_t total = sizeof(fsa_mounts) / sizeof(fsa_mounts[0]);
+
+ fsaInit();
+
+ for (uint32_t i = 0; i < total; i++) {
+ FSADeviceData *mount = &fsa_mounts[i];
+ if (!mount->setup) {
+ continue;
+ }
+ if (strcmp(mount->name, virt_name) == 0) {
+ RemoveDevice(mount->name);
+ fsa_free(mount);
+ return MOCHA_RESULT_SUCCESS;
+ }
+ }
+
+ DEBUG_FUNCTION_LINE_ERR("Failed to find fsa mount data for %s", virt_name);
+ return MOCHA_RESULT_NOT_FOUND;
+}
+extern int mochaInitDone;
+
+MochaUtilsStatus Mocha_MountFS(const char *virt_name, const char *dev_path, const char *mount_path) {
+ return Mocha_MountFSEx(virt_name, dev_path, mount_path, FSA_MOUNT_FLAG_GLOBAL_MOUNT, nullptr, 0);
+}
+
+MochaUtilsStatus Mocha_MountFSEx(const char *virt_name, const char *dev_path, const char *mount_path, FSAMountFlags mountFlags, void *mountArgBuf, int mountArgBufLen) {
+ if (!mochaInitDone) {
+ if (Mocha_InitLibrary() != MOCHA_RESULT_SUCCESS) {
+ DEBUG_FUNCTION_LINE_ERR("Mocha_InitLibrary failed");
+ return MOCHA_RESULT_UNSUPPORTED_COMMAND;
+ }
+ }
+
+ FSAInit();
+ std::lock_guard lock(fsaMutex);
+
+ FSADeviceData *mount = fsa_alloc();
+ if (mount == nullptr) {
+ DEBUG_FUNCTION_LINE_ERR("fsa_alloc() failed");
+ OSMemoryBarrier();
+ return MOCHA_RESULT_OUT_OF_MEMORY;
+ }
+
+ mount->clientHandle = FSAAddClient(nullptr);
+ if (mount->clientHandle < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAAddClient() failed: %s", FSAGetStatusStr(static_cast(mount->clientHandle)));
+ fsa_free(mount);
+ return MOCHA_RESULT_MAX_CLIENT;
+ }
+
+ if (Mocha_UnlockFSClientEx(mount->clientHandle) != MOCHA_RESULT_SUCCESS) {
+ DEBUG_FUNCTION_LINE_ERR("Mocha_UnlockFSClientEx failed");
+ return MOCHA_RESULT_UNSUPPORTED_COMMAND;
+ }
+
+ mount->mounted = false;
+
+ strncpy(mount->name, virt_name, sizeof(mount->name) - 1);
+ strncpy(mount->mount_path, mount_path, sizeof(mount->mount_path) - 1);
+ FSError res;
+ if (dev_path) {
+ res = FSAMount(mount->clientHandle, dev_path, mount_path, mountFlags, mountArgBuf, mountArgBufLen);
+ if (res < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAMount(0x%08X, %s, %s, FSA_MOUNT_FLAG_GLOBAL_MOUNT, nullptr, 0) failed: %s", mount->clientHandle, dev_path, mount_path, FSAGetStatusStr(res));
+ fsa_free(mount);
+ if (res == FS_ERROR_ALREADY_EXISTS) {
+ return MOCHA_RESULT_ALREADY_EXISTS;
+ }
+ return MOCHA_RESULT_UNKNOWN_ERROR;
+ }
+ mount->mounted = true;
+ } else {
+ mount->mounted = false;
+ }
+
+ if ((res = FSAChangeDir(mount->clientHandle, mount->mount_path)) < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAChangeDir(0x%08X, %s) failed: %s", mount->clientHandle, mount->mount_path, FSAGetStatusStr(res));
+ }
+
+ FSADeviceInfo deviceInfo;
+ if ((res = FSAGetDeviceInfo(mount->clientHandle, mount_path, &deviceInfo)) >= 0) {
+ mount->deviceSizeInSectors = deviceInfo.deviceSizeInSectors;
+ mount->deviceSectorSize = deviceInfo.deviceSectorSize;
+ } else {
+ mount->deviceSizeInSectors = 0xFFFFFFFF;
+ mount->deviceSectorSize = 512;
+ DEBUG_FUNCTION_LINE_ERR("Failed to get DeviceInfo for %s: %s", mount_path, FSAGetStatusStr(res));
+ }
+
+ if (AddDevice(&mount->device) < 0) {
+ DEBUG_FUNCTION_LINE_ERR("AddDevice failed for %s.", virt_name);
+ fsa_free(mount);
+ return MOCHA_RESULT_ADD_DEVOPTAB_FAILED;
+ }
+
+ mount->setup = true;
+
+ return MOCHA_RESULT_SUCCESS;
+}
diff --git a/source/devoptab/devoptab_fs.h b/source/devoptab/devoptab_fs.h
new file mode 100644
index 0000000..bd815e9
--- /dev/null
+++ b/source/devoptab/devoptab_fs.h
@@ -0,0 +1,109 @@
+#pragma once
+#include
+
+#include "FastLockWrapper.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+typedef struct FSADeviceData {
+ devoptab_t device{};
+ bool setup{};
+ bool mounted{};
+ uint32_t id{};
+ char name[32]{};
+ char mount_path[256]{};
+ uint64_t deviceSizeInSectors{};
+ uint32_t deviceSectorSize{};
+ FSAClientHandle clientHandle{};
+ FastLockWrapper mutex;
+} FSADeviceData;
+
+/**
+ * Open file struct
+ */
+typedef struct
+{
+ //! FS handle
+ FSAFileHandle fd;
+
+ //! Flags used in open(2)
+ int flags;
+
+ //! Current file offset
+ uint32_t offset;
+
+ //! Path stored for internal path tracking
+ char path[FS_MAX_PATH + 1];
+} __fsa_file_t;
+
+/**
+ * Open directory struct
+ */
+typedef struct
+{
+ //! Should be set to FS_DIRITER_MAGIC
+ uint32_t magic;
+
+ //! FS handle
+ FSADirectoryHandle fd;
+
+ //! Temporary storage for reading entries
+ FSADirectoryEntry entry_data;
+} __fsa_dir_t;
+
+#define FS_DIRITER_MAGIC 0x77696975
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int __fsa_open(struct _reent *r, void *fileStruct, const char *path,
+ int flags, int mode);
+int __fsa_close(struct _reent *r, void *fd);
+ssize_t __fsa_write(struct _reent *r, void *fd, const char *ptr,
+ size_t len);
+ssize_t __fsa_read(struct _reent *r, void *fd, char *ptr, size_t len);
+off_t __fsa_seek(struct _reent *r, void *fd, off_t pos, int dir);
+int __fsa_fstat(struct _reent *r, void *fd, struct stat *st);
+int __fsa_stat(struct _reent *r, const char *file, struct stat *st);
+int __fsa_link(struct _reent *r, const char *existing,
+ const char *newLink);
+int __fsa_unlink(struct _reent *r, const char *name);
+int __fsa_chdir(struct _reent *r, const char *name);
+int __fsa_rename(struct _reent *r, const char *oldName,
+ const char *newName);
+int __fsa_mkdir(struct _reent *r, const char *path, int mode);
+DIR_ITER *__fsa_diropen(struct _reent *r, DIR_ITER *dirState,
+ const char *path);
+int __fsa_dirreset(struct _reent *r, DIR_ITER *dirState);
+int __fsa_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename,
+ struct stat *filestat);
+int __fsa_dirclose(struct _reent *r, DIR_ITER *dirState);
+int __fsa_statvfs(struct _reent *r, const char *path,
+ struct statvfs *buf);
+int __fsa_ftruncate(struct _reent *r, void *fd, off_t len);
+int __fsa_fsync(struct _reent *r, void *fd);
+int __fsa_chmod(struct _reent *r, const char *path, mode_t mode);
+int __fsa_fchmod(struct _reent *r, void *fd, mode_t mode);
+int __fsa_rmdir(struct _reent *r, const char *name);
+int __fsa_utimes(struct _reent *r, const char *filename, const struct timeval times[2]);
+
+// devoptab_fs_utils.c
+char *__fsa_fixpath(struct _reent *r, const char *path);
+int __fsa_translate_error(FSError error);
+time_t __fsa_translate_time(FSTime timeValue);
+FSMode __fsa_translate_permission_mode(int mode);
+mode_t __fsa_translate_stat_mode(FSStat *fileStat);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/source/devoptab/devoptab_fsa_chdir.cpp b/source/devoptab/devoptab_fsa_chdir.cpp
new file mode 100644
index 0000000..c3984a5
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_chdir.cpp
@@ -0,0 +1,32 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_chdir(struct _reent *r,
+ const char *path) {
+ FSError status;
+
+ if (!path) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ char *fixedPath = __fsa_fixpath(r, path);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAChangeDir(deviceData->clientHandle, fixedPath);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAChangeDir(0x%08X, %s) failed: %s", deviceData->clientHandle, fixedPath, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+ free(fixedPath);
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_chmod.cpp b/source/devoptab/devoptab_fsa_chmod.cpp
new file mode 100644
index 0000000..67cb152
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_chmod.cpp
@@ -0,0 +1,38 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+#include
+
+int __fsa_chmod(struct _reent *r,
+ const char *path,
+ mode_t mode) {
+ FSError status;
+
+ if (!path) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ char *fixedPath = __fsa_fixpath(r, path);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ FSMode translatedMode = __fsa_translate_permission_mode(mode);
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAChangeMode(deviceData->clientHandle, fixedPath, translatedMode);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAChangeMode(0x%08X, %s, 0x%X) failed: %s", deviceData->clientHandle, fixedPath, translatedMode, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+ free(fixedPath);
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_close.cpp b/source/devoptab/devoptab_fsa_close.cpp
new file mode 100644
index 0000000..0fe482c
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_close.cpp
@@ -0,0 +1,29 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_close(struct _reent *r,
+ void *fd) {
+ FSError status;
+ __fsa_file_t *file;
+
+ if (!fd) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ file = (__fsa_file_t *) fd;
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSACloseFile(deviceData->clientHandle, file->fd);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSACloseFile(0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, file->fd, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_dirclose.cpp b/source/devoptab/devoptab_fsa_dirclose.cpp
new file mode 100644
index 0000000..f9ee986
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_dirclose.cpp
@@ -0,0 +1,29 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_dirclose(struct _reent *r,
+ DIR_ITER *dirState) {
+ FSError status;
+ __fsa_dir_t *dir;
+
+ if (!dirState) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ dir = (__fsa_dir_t *) (dirState->dirStruct);
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSACloseDir(deviceData->clientHandle, dir->fd);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSACloseDir(0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, dir->fd, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_dirnext.cpp b/source/devoptab/devoptab_fsa_dirnext.cpp
new file mode 100644
index 0000000..3cfc19f
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_dirnext.cpp
@@ -0,0 +1,52 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_dirnext(struct _reent *r,
+ DIR_ITER *dirState,
+ char *filename,
+ struct stat *filestat) {
+ FSError status;
+ __fsa_dir_t *dir;
+
+ if (!dirState || !filename || !filestat) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ dir = (__fsa_dir_t *) (dirState->dirStruct);
+ memset(&dir->entry_data, 0, sizeof(dir->entry_data));
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAReadDir(deviceData->clientHandle, dir->fd, &dir->entry_data);
+ if (status < 0) {
+ if (status != FS_ERROR_END_OF_DIR) {
+ DEBUG_FUNCTION_LINE_ERR("FSAReadDir(0x%08X, 0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, dir->fd, &dir->entry_data, FSAGetStatusStr(status));
+ }
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ // Fill in the stat info
+ memset(filestat, 0, sizeof(struct stat));
+ filestat->st_dev = (dev_t) deviceData->clientHandle;
+ filestat->st_ino = dir->entry_data.info.entryId;
+ filestat->st_mode = __fsa_translate_stat_mode(&dir->entry_data.info);
+ filestat->st_nlink = 1;
+ filestat->st_uid = dir->entry_data.info.owner;
+ filestat->st_gid = dir->entry_data.info.group;
+ filestat->st_rdev = filestat->st_dev;
+ filestat->st_size = dir->entry_data.info.size;
+ filestat->st_blksize = deviceData->deviceSectorSize;
+ filestat->st_blocks = (filestat->st_size + filestat->st_blksize - 1) / filestat->st_blksize;
+ filestat->st_atime = __fsa_translate_time(dir->entry_data.info.modified);
+ filestat->st_ctime = __fsa_translate_time(dir->entry_data.info.created);
+ filestat->st_mtime = __fsa_translate_time(dir->entry_data.info.modified);
+
+ memset(filename, 0, NAME_MAX);
+ strcpy(filename, dir->entry_data.name);
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_diropen.cpp b/source/devoptab/devoptab_fsa_diropen.cpp
new file mode 100644
index 0000000..25f0b34
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_diropen.cpp
@@ -0,0 +1,41 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+DIR_ITER *
+__fsa_diropen(struct _reent *r,
+ DIR_ITER *dirState,
+ const char *path) {
+ FSADirectoryHandle fd;
+ FSError status;
+ __fsa_dir_t *dir;
+
+ if (!dirState || !path) {
+ r->_errno = EINVAL;
+ return NULL;
+ }
+
+ char *fixedPath = __fsa_fixpath(r, path);
+ if (!fixedPath) {
+ return NULL;
+ }
+ dir = (__fsa_dir_t *) (dirState->dirStruct);
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAOpenDir(deviceData->clientHandle, fixedPath, &fd);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAOpenDir(0x%08X, %s, 0x%08X) failed: %s", deviceData->clientHandle, fixedPath, &fd, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return NULL;
+ }
+ free(fixedPath);
+
+ dir->magic = FS_DIRITER_MAGIC;
+ dir->fd = fd;
+ memset(&dir->entry_data, 0, sizeof(dir->entry_data));
+ return dirState;
+}
diff --git a/source/devoptab/devoptab_fsa_dirreset.cpp b/source/devoptab/devoptab_fsa_dirreset.cpp
new file mode 100644
index 0000000..d7372c5
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_dirreset.cpp
@@ -0,0 +1,28 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_dirreset(struct _reent *r,
+ DIR_ITER *dirState) {
+ FSError status;
+ __fsa_dir_t *dir;
+
+ if (!dirState) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ dir = (__fsa_dir_t *) (dirState->dirStruct);
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSARewindDir(deviceData->clientHandle, dir->fd);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSARewindDir(0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, dir->fd, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_fchmod.cpp b/source/devoptab/devoptab_fsa_fchmod.cpp
new file mode 100644
index 0000000..dd89934
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_fchmod.cpp
@@ -0,0 +1,27 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_fchmod(struct _reent *r,
+ void *fd,
+ mode_t mode) {
+ FSError status;
+ __fsa_file_t *file;
+
+ file = (__fsa_file_t *) fd;
+
+ FSMode translatedMode = __fsa_translate_permission_mode(mode);
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAChangeMode(deviceData->clientHandle, file->path, translatedMode);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAChangeMode(0x%08X, %s, 0x%X) failed: %s", deviceData->clientHandle, file->path, translatedMode, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_fstat.cpp b/source/devoptab/devoptab_fsa_fstat.cpp
new file mode 100644
index 0000000..77c0e7d
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_fstat.cpp
@@ -0,0 +1,41 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_fstat(struct _reent *r,
+ void *fd,
+ struct stat *st) {
+ FSError status;
+ FSAStat fsStat;
+ __fsa_file_t *file;
+
+ if (!fd || !st) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ file = (__fsa_file_t *) fd;
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAGetStatFile(deviceData->clientHandle, file->fd, &fsStat);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSARewindDir(0x%08X, 0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, file->fd, &fsStat, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ memset(st, 0, sizeof(struct stat));
+ st->st_size = fsStat.size;
+ st->st_uid = fsStat.owner;
+ st->st_gid = fsStat.group;
+ st->st_nlink = 1;
+ st->st_mode = __fsa_translate_stat_mode(&fsStat);
+ st->st_atime = __fsa_translate_time(fsStat.modified);
+ st->st_ctime = __fsa_translate_time(fsStat.created);
+ st->st_mtime = __fsa_translate_time(fsStat.modified);
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_fsync.cpp b/source/devoptab/devoptab_fsa_fsync.cpp
new file mode 100644
index 0000000..43c9ec2
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_fsync.cpp
@@ -0,0 +1,29 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_fsync(struct _reent *r,
+ void *fd) {
+ FSError status;
+ __fsa_file_t *file;
+
+ if (!fd) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ file = (__fsa_file_t *) fd;
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAFlushFile(deviceData->clientHandle, file->fd);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAFlushFile(0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, file->fd, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_link.cpp b/source/devoptab/devoptab_fsa_link.cpp
new file mode 100644
index 0000000..8680fed
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_link.cpp
@@ -0,0 +1,8 @@
+#include "devoptab_fs.h"
+
+int __fsa_link(struct _reent *r,
+ const char *existing,
+ const char *newLink) {
+ r->_errno = ENOSYS;
+ return -1;
+}
diff --git a/source/devoptab/devoptab_fsa_mkdir.cpp b/source/devoptab/devoptab_fsa_mkdir.cpp
new file mode 100644
index 0000000..6108f15
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_mkdir.cpp
@@ -0,0 +1,38 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_mkdir(struct _reent *r,
+ const char *path,
+ int mode) {
+ FSError status;
+ char *fixedPath;
+
+ if (!path) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ fixedPath = __fsa_fixpath(r, path);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ FSMode translatedMode = __fsa_translate_permission_mode(mode);
+
+ status = FSAMakeDir(deviceData->clientHandle, fixedPath, translatedMode);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAMakeDir(0x%08X, %s, 0x%X) failed: %s", deviceData->clientHandle, fixedPath, translatedMode, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+ free(fixedPath);
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_open.cpp b/source/devoptab/devoptab_fsa_open.cpp
new file mode 100644
index 0000000..6a49b81
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_open.cpp
@@ -0,0 +1,95 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+// Use this extended hidden flag value to provide FS_OPEN_FLAG_ENCRYPTED in underlying FSOpenFileEx() call
+#define O_ENCRYPTED 0x4000000
+
+int __fsa_open(struct _reent *r,
+ void *fileStruct,
+ const char *path,
+ int flags,
+ int mode) {
+ FSAFileHandle fd;
+ FSError status;
+ const char *fsMode;
+ __fsa_file_t *file;
+
+ if (!fileStruct || !path) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ // Map flags to open modes
+ if ((flags & O_ACCMODE) == O_RDONLY) {
+ fsMode = "r";
+ if (flags & O_APPEND) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+ } else if ((flags & O_ACCMODE) == O_WRONLY) {
+ if (flags & O_APPEND) {
+ fsMode = "a";
+ } else if (flags & O_TRUNC) {
+ fsMode = "w";
+ } else {
+ r->_errno = EINVAL;
+ return -1;
+ }
+ } else if ((flags & O_ACCMODE) == O_RDWR) {
+ if (flags & O_APPEND) {
+ fsMode = "a+";
+ } else if (flags & O_TRUNC) {
+ fsMode = "w+";
+ } else {
+ fsMode = "r+";
+ }
+ } else {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ char *fixedPath = __fsa_fixpath(r, path);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ // Open the file
+ FSOpenFileFlags openFlags = (mode & O_ENCRYPTED) ? FS_OPEN_FLAG_ENCRYPTED : FS_OPEN_FLAG_NONE;
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ FSMode translatedMode = __fsa_translate_permission_mode(mode);
+
+ uint32_t preAllocSize = 0;
+
+ status = FSAOpenFileEx(deviceData->clientHandle, fixedPath, fsMode, translatedMode, openFlags, preAllocSize, &fd);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAOpenFileEx(0x%08X, %s, %s, 0x%X, 0x%08X, 0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, fixedPath, fsMode, translatedMode, openFlags, preAllocSize, &fd, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ free(fixedPath);
+ return -1;
+ }
+
+ file = (__fsa_file_t *) fileStruct;
+ file->fd = fd;
+ file->flags = (flags & (O_ACCMODE | O_APPEND | O_SYNC));
+ strncpy(file->path, fixedPath, FS_MAX_PATH);
+ free(fixedPath);
+
+ if (flags & O_APPEND) {
+ status = FSAGetPosFile(deviceData->clientHandle, fd, &file->offset);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAGetPosFile(0x%08X, 0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, fd, &file->offset, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ if (FSACloseFile(deviceData->clientHandle, fd) < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSACloseFile(0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, fd, FSAGetStatusStr(status));
+ }
+ return -1;
+ }
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/source/devoptab/devoptab_fsa_read.cpp b/source/devoptab/devoptab_fsa_read.cpp
new file mode 100644
index 0000000..0fb4ff5
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_read.cpp
@@ -0,0 +1,77 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+
+#include
+#include
+
+ssize_t __fsa_read(struct _reent *r, void *fd, char *ptr, size_t len) {
+ if (!fd || !ptr) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ // Check that the file was opened with read access
+ __fsa_file_t *file = (__fsa_file_t *) fd;
+ if ((file->flags & O_ACCMODE) == O_WRONLY) {
+ r->_errno = EBADF;
+ return -1;
+ }
+
+ // cache-aligned, cache-line-sized
+ __attribute__((aligned(0x40))) uint8_t alignedBuffer[0x40];
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ size_t bytesRead = 0;
+ while (bytesRead < len) {
+ // only use input buffer if cache-aligned and read size is a multiple of cache line size
+ // otherwise read into alignedBuffer
+ uint8_t *tmp = (uint8_t *) ptr;
+ size_t size = len - bytesRead;
+
+ if ((uintptr_t) ptr & 0x3F) {
+ // read partial cache-line front-end
+ tmp = alignedBuffer;
+ size = MIN(size, 0x40 - ((uintptr_t) ptr & 0x3F));
+ } else if (size < 0x40) {
+ // read partial cache-line back-end
+ tmp = alignedBuffer;
+ } else {
+ // read whole cache lines
+ size &= ~0x3F;
+ }
+
+ // Limit each request to 1 MiB
+ if (size > 0x100000) {
+ size = 0x100000;
+ }
+
+ FSError status = FSAReadFile(deviceData->clientHandle, tmp, 1, size, file->fd, 0);
+
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAReadFile(0x%08X, 0x%08X, 1, 0x%08X, 0x%08X, 0) failed: %s", deviceData->clientHandle, tmp, size, file->fd, FSAGetStatusStr(status));
+ if (bytesRead != 0) {
+ return bytesRead; // error after partial read
+ }
+
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ if (tmp == alignedBuffer) {
+ memcpy(ptr, alignedBuffer, status);
+ }
+
+ bytesRead += status;
+ ptr += status;
+
+
+ if ((size_t) status != size) {
+ return bytesRead; // partial read
+ }
+ }
+
+ return bytesRead;
+}
\ No newline at end of file
diff --git a/source/devoptab/devoptab_fsa_rename.cpp b/source/devoptab/devoptab_fsa_rename.cpp
new file mode 100644
index 0000000..4b295e9
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_rename.cpp
@@ -0,0 +1,45 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_rename(struct _reent *r,
+ const char *oldName,
+ const char *newName) {
+ FSError status;
+ char *fixedOldPath, *fixedNewPath;
+
+ if (!oldName || !newName) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ fixedOldPath = __fsa_fixpath(r, oldName);
+ if (!fixedOldPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ fixedNewPath = __fsa_fixpath(r, newName);
+ if (!fixedNewPath) {
+ free(fixedOldPath);
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSARename(deviceData->clientHandle, fixedOldPath, fixedNewPath);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSARename(0x%08X, %s, %s) failed: %s", deviceData->clientHandle, fixedOldPath, fixedNewPath, FSAGetStatusStr(status));
+ free(fixedOldPath);
+ free(fixedNewPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+ free(fixedOldPath);
+ free(fixedNewPath);
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_rmdir.cpp b/source/devoptab/devoptab_fsa_rmdir.cpp
new file mode 100644
index 0000000..7cd180d
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_rmdir.cpp
@@ -0,0 +1,71 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_rmdir(struct _reent *r,
+ const char *name) {
+ FSError status;
+
+ if (!name) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ char *fixedPath = __fsa_fixpath(r, name);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ // Check if directory still has files in which case return error
+ FSDirectoryHandle dirHandle = 0;
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAOpenDir(deviceData->clientHandle, fixedPath, &dirHandle);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAOpenDir(0x%08X, %s, 0x%08X) failed: %s", deviceData->clientHandle, fixedPath, &dirHandle, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ auto *dir_entry = (FSADirectoryEntry *) malloc(sizeof(FSADirectoryEntry));
+ if (dir_entry == nullptr) {
+ DEBUG_FUNCTION_LINE_ERR("malloc(sizeof(FSADirectoryEntry)) failed");
+ free(fixedPath);
+ FSACloseDir(deviceData->clientHandle, dirHandle);
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ status = FSAReadDir(deviceData->clientHandle, dirHandle, dir_entry);
+ FSACloseDir(deviceData->clientHandle, dirHandle);
+ if (status == FS_ERROR_OK) {
+ free(fixedPath);
+ free(dir_entry);
+ r->_errno = ENOTEMPTY;
+ return -1;
+ } else if (status != FS_ERROR_END_OF_DIR) {
+ DEBUG_FUNCTION_LINE_ERR("FSAReadDir(0x%08X, 0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, dirHandle, dir_entry, FSAGetStatusStr(status));
+ free(fixedPath);
+ free(dir_entry);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+ free(dir_entry);
+
+ // Remove folder since folder is empty
+ status = FSARemove(deviceData->clientHandle, fixedPath);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSARemove(0x%08X, %s) failed: %s", deviceData->clientHandle, fixedPath, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+ free(fixedPath);
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_seek.cpp b/source/devoptab/devoptab_fsa_seek.cpp
new file mode 100644
index 0000000..182d891
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_seek.cpp
@@ -0,0 +1,73 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+off_t __fsa_seek(struct _reent *r,
+ void *fd,
+ off_t pos,
+ int whence) {
+ FSError status;
+ FSAStat fsStat;
+ uint64_t offset;
+ __fsa_file_t *file;
+
+ if (!fd) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ file = (__fsa_file_t *) fd;
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+ status = FSAGetStatFile(deviceData->clientHandle, file->fd, &fsStat);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAGetStatFile(0x%08X, 0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, file->fd, &fsStat, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ // Find the offset to see from
+ switch (whence) {
+ // Set absolute position; start offset is 0
+ case SEEK_SET:
+ offset = 0;
+ break;
+
+ // Set position relative to the current position
+ case SEEK_CUR:
+ offset = file->offset;
+ break;
+
+ // Set position relative to the end of the file
+ case SEEK_END:
+ offset = fsStat.size;
+ break;
+
+ // An invalid option was provided
+ default:
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ // TODO: A better check that prevents overflow.
+ if (pos < 0 && (off_t) offset < -pos) {
+ // Don't allow seek to before the beginning of the file
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ uint32_t old_pos = file->offset;
+ file->offset = offset + pos;
+
+ status = FSASetPosFile(deviceData->clientHandle, file->fd, file->offset);
+ if (status < 0) {
+ file->offset = old_pos;
+ DEBUG_FUNCTION_LINE_ERR("FSASetPosFile(0x%08X, 0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, file->fd, file->offset, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ return file->offset;
+}
diff --git a/source/devoptab/devoptab_fsa_stat.cpp b/source/devoptab/devoptab_fsa_stat.cpp
new file mode 100644
index 0000000..75cc7dc
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_stat.cpp
@@ -0,0 +1,50 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_stat(struct _reent *r,
+ const char *path,
+ struct stat *st) {
+ FSError status;
+ FSAStat fsStat;
+
+ if (!path || !st) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ char *fixedPath = __fsa_fixpath(r, path);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSAGetStat(deviceData->clientHandle, fixedPath, &fsStat);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAGetStat(0x%08X, %s, 0x%08X) failed: %s", deviceData->clientHandle, fixedPath, &fsStat, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+ free(fixedPath);
+
+ memset(st, 0, sizeof(struct stat));
+ st->st_dev = (dev_t) deviceData->clientHandle;
+ st->st_ino = fsStat.entryId;
+ st->st_mode = __fsa_translate_stat_mode(&fsStat);
+ st->st_nlink = 1;
+ st->st_uid = fsStat.owner;
+ st->st_gid = fsStat.group;
+ st->st_rdev = st->st_dev;
+ st->st_size = fsStat.size;
+ st->st_blksize = deviceData->deviceSectorSize;
+ st->st_blocks = (st->st_size + st->st_blksize - 1) / st->st_blksize;
+ st->st_atime = __fsa_translate_time(fsStat.modified);
+ st->st_ctime = __fsa_translate_time(fsStat.created);
+ st->st_mtime = __fsa_translate_time(fsStat.modified);
+ return 0;
+}
\ No newline at end of file
diff --git a/source/devoptab/devoptab_fsa_statvfs.cpp b/source/devoptab/devoptab_fsa_statvfs.cpp
new file mode 100644
index 0000000..5fee11b
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_statvfs.cpp
@@ -0,0 +1,50 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_statvfs(struct _reent *r,
+ const char *path,
+ struct statvfs *buf) {
+ FSError status;
+ uint64_t freeSpace;
+ memset(buf, 0, sizeof(struct statvfs));
+
+ char *fixedPath = __fsa_fixpath(r, path);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+ status = FSAGetFreeSpaceSize(deviceData->clientHandle, fixedPath, &freeSpace);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAGetFreeSpaceSize(0x%08X, %s, 0x%08X) failed: %s", deviceData->clientHandle, fixedPath, &freeSpace, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+ free(fixedPath);
+
+ // File system block size
+ buf->f_bsize = deviceData->deviceSectorSize;
+ // Fundamental file system block size
+ buf->f_frsize = deviceData->deviceSectorSize;
+ // Total number of blocks on file system in units of f_frsize
+ buf->f_blocks = deviceData->deviceSizeInSectors;
+ // Free blocks available for all and for non-privileged processes
+ buf->f_bfree = buf->f_bavail = (uint32_t) (freeSpace / buf->f_frsize);
+ // Number of inodes at this point in time
+ buf->f_files = 0xFFFFFFFF;
+ // Free inodes available for all and for non-privileged processes
+ buf->f_ffree = 0xFFFFFFFF;
+ // File system id
+ buf->f_fsid = (unsigned long) deviceData->clientHandle;
+ // Bit mask of f_flag values.
+ buf->f_flag = 0;
+ // Maximum length of filenames
+ buf->f_namemax = 255;
+
+ return 0;
+}
\ No newline at end of file
diff --git a/source/devoptab/devoptab_fsa_truncate.cpp b/source/devoptab/devoptab_fsa_truncate.cpp
new file mode 100644
index 0000000..ac030de
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_truncate.cpp
@@ -0,0 +1,38 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_ftruncate(struct _reent *r,
+ void *fd,
+ off_t len) {
+ FSError status;
+ __fsa_file_t *file;
+
+ // Make sure length is non-negative
+ if (!fd || len < 0) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ // Set the new file size
+ file = (__fsa_file_t *) fd;
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+ status = FSASetPosFile(deviceData->clientHandle, file->fd, len);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSASetPosFile(0x%08X, 0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, file->fd, len, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ status = FSATruncateFile(deviceData->clientHandle, file->fd);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSATruncateFile(0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, file->fd, FSAGetStatusStr(status));
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_unlink.cpp b/source/devoptab/devoptab_fsa_unlink.cpp
new file mode 100644
index 0000000..60c517b
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_unlink.cpp
@@ -0,0 +1,35 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+int __fsa_unlink(struct _reent *r,
+ const char *name) {
+ FSError status;
+ char *fixedPath;
+
+ if (!name) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ fixedPath = __fsa_fixpath(r, name);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return -1;
+ }
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ status = FSARemove(deviceData->clientHandle, fixedPath);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSARemove(0x%08X, %s) failed: %s", deviceData->clientHandle, fixedPath, FSAGetStatusStr(status));
+ free(fixedPath);
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ free(fixedPath);
+
+ return 0;
+}
diff --git a/source/devoptab/devoptab_fsa_utils.cpp b/source/devoptab/devoptab_fsa_utils.cpp
new file mode 100644
index 0000000..4afe577
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_utils.cpp
@@ -0,0 +1,169 @@
+#include "devoptab_fs.h"
+
+char *
+__fsa_fixpath(struct _reent *r,
+ const char *path) {
+ char *p;
+ char *fixedPath;
+
+ if (!path) {
+ r->_errno = EINVAL;
+ return nullptr;
+ }
+
+ p = strchr(path, ':') + 1;
+ if (!strchr(path, ':')) {
+ p = (char *) path;
+ }
+
+ if (strlen(p) > FS_MAX_PATH) {
+ r->_errno = ENAMETOOLONG;
+ return nullptr;
+ }
+
+ fixedPath = (char *) memalign(0x40, FS_MAX_PATH + 1);
+ if (!fixedPath) {
+ r->_errno = ENOMEM;
+ return nullptr;
+ }
+
+ if (p[0] == '/') {
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+ strcpy(fixedPath, deviceData->mount_path);
+ strcat(fixedPath, p);
+ } else {
+ strcpy(fixedPath, p);
+ }
+
+ return fixedPath;
+}
+
+mode_t __fsa_translate_stat_mode(FSStat *fileStat) {
+ mode_t retMode = 0;
+
+ if ((fileStat->flags & FS_STAT_LINK) == FS_STAT_LINK) {
+ retMode |= S_IFLNK;
+ } else if ((fileStat->flags & FS_STAT_DIRECTORY) == FS_STAT_DIRECTORY) {
+ retMode |= S_IFDIR;
+ } else if ((fileStat->flags & FS_STAT_FILE) == FS_STAT_FILE) {
+ retMode |= S_IFREG;
+ } else if (fileStat->size > 0) {
+ // Some regular Wii U files have no type info but will have a size
+ retMode |= S_IFREG;
+ }
+
+ // Convert normal CafeOS hexadecimal permission bits into Unix octal permission bits
+ mode_t permissionMode = (((fileStat->mode >> 2) & S_IRWXU) | ((fileStat->mode >> 1) & S_IRWXG) | (fileStat->mode & S_IRWXO));
+
+ return retMode | permissionMode;
+}
+
+// The Wii U FSTime epoch is at 1980, so we must map it to 1970 for gettime
+#define WIIU_FSTIME_EPOCH_YEAR (1980)
+
+#define EPOCH_YEAR (1970)
+#define EPOCH_YEARS_SINCE_LEAP 2
+#define EPOCH_YEARS_SINCE_CENTURY 70
+#define EPOCH_YEARS_SINCE_LEAP_CENTURY 370
+
+#define EPOCH_DIFF_YEARS(year) (year - EPOCH_YEAR)
+#define EPOCH_DIFF_DAYS(year) \
+ ((EPOCH_DIFF_YEARS(year) * 365) + \
+ (EPOCH_DIFF_YEARS(year) - 1 + EPOCH_YEARS_SINCE_LEAP) / 4 - \
+ (EPOCH_DIFF_YEARS(year) - 1 + EPOCH_YEARS_SINCE_CENTURY) / 100 + \
+ (EPOCH_DIFF_YEARS(year) - 1 + EPOCH_YEARS_SINCE_LEAP_CENTURY) / 400)
+#define EPOCH_DIFF_SECS(year) (60ull * 60ull * 24ull * (uint64_t) EPOCH_DIFF_DAYS(year))
+
+time_t __fsa_translate_time(FSTime timeValue) {
+ return (timeValue / 1000000) + EPOCH_DIFF_SECS(WIIU_FSTIME_EPOCH_YEAR);
+}
+
+
+FSMode
+__fsa_translate_permission_mode(int mode) {
+ // Convert normal Unix octal permission bits into CafeOS hexadecimal permission bits
+ return (FSMode) (((mode & S_IRWXU) << 2) | ((mode & S_IRWXG) << 1) | (mode & S_IRWXO));
+}
+
+
+int __fsa_translate_error(FSError error) {
+ switch (error) {
+ case FS_ERROR_END_OF_DIR:
+ case FS_ERROR_END_OF_FILE:
+ return ENOENT;
+ case FS_ERROR_ALREADY_EXISTS:
+ return EEXIST;
+ case FS_ERROR_MEDIA_ERROR:
+ return EIO;
+ case FS_ERROR_NOT_FOUND:
+ return ENOENT;
+ case FS_ERROR_PERMISSION_ERROR:
+ return EPERM;
+ case FS_ERROR_STORAGE_FULL:
+ return ENOSPC;
+ case FS_ERROR_BUSY:
+ return EBUSY;
+ case FS_ERROR_CANCELLED:
+ return ECANCELED;
+ case FS_ERROR_FILE_TOO_BIG:
+ return EFBIG;
+ case FS_ERROR_INVALID_PATH:
+ return ENAMETOOLONG;
+ case FS_ERROR_NOT_DIR:
+ return ENOTDIR;
+ case FS_ERROR_NOT_FILE:
+ return EISDIR;
+ case FS_ERROR_OUT_OF_RANGE:
+ return ESPIPE;
+ case FS_ERROR_UNSUPPORTED_COMMAND:
+ return ENOTSUP;
+ case FS_ERROR_WRITE_PROTECTED:
+ return EROFS;
+ case FS_ERROR_NOT_INIT:
+ return ENODEV;
+ // TODO
+ case FS_ERROR_MAX_MOUNT_POINTS:
+ break;
+ case FS_ERROR_MAX_VOLUMES:
+ break;
+ case FS_ERROR_MAX_CLIENTS:
+ break;
+ case FS_ERROR_MAX_FILES:
+ break;
+ case FS_ERROR_MAX_DIRS:
+ break;
+ case FS_ERROR_ALREADY_OPEN:
+ break;
+ case FS_ERROR_NOT_EMPTY:
+ break;
+ case FS_ERROR_ACCESS_ERROR:
+ break;
+ case FS_ERROR_DATA_CORRUPTED:
+ break;
+ case FS_ERROR_JOURNAL_FULL:
+ break;
+ case FS_ERROR_UNAVAILABLE_COMMAND:
+ break;
+ case FS_ERROR_INVALID_PARAM:
+ break;
+ case FS_ERROR_INVALID_BUFFER:
+ break;
+ case FS_ERROR_INVALID_ALIGNMENT:
+ break;
+ case FS_ERROR_INVALID_CLIENTHANDLE:
+ break;
+ case FS_ERROR_INVALID_FILEHANDLE:
+ break;
+ case FS_ERROR_INVALID_DIRHANDLE:
+ break;
+ case FS_ERROR_OUT_OF_RESOURCES:
+ break;
+ case FS_ERROR_MEDIA_NOT_READY:
+ break;
+ case FS_ERROR_INVALID_MEDIA:
+ break;
+ default:
+ break;
+ }
+ return (int) EIO;
+}
diff --git a/source/devoptab/devoptab_fsa_utimes.cpp b/source/devoptab/devoptab_fsa_utimes.cpp
new file mode 100644
index 0000000..ae60d1d
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_utimes.cpp
@@ -0,0 +1,9 @@
+#include "devoptab_fs.h"
+
+int __fsa_utimes(struct _reent *r,
+ const char *filename,
+ const struct timeval times[2]) {
+ // TODO: FSChangeMode and FSStatFile?
+ r->_errno = ENOSYS;
+ return -1;
+}
diff --git a/source/devoptab/devoptab_fsa_write.cpp b/source/devoptab/devoptab_fsa_write.cpp
new file mode 100644
index 0000000..8867f67
--- /dev/null
+++ b/source/devoptab/devoptab_fsa_write.cpp
@@ -0,0 +1,73 @@
+#include "devoptab_fs.h"
+#include "logger.h"
+#include
+
+ssize_t __fsa_write(struct _reent *r, void *fd, const char *ptr, size_t len) {
+ if (!fd || !ptr) {
+ r->_errno = EINVAL;
+ return -1;
+ }
+
+ // Check that the file was opened with write access
+ __fsa_file_t *file = (__fsa_file_t *) fd;
+ if ((file->flags & O_ACCMODE) == O_RDONLY) {
+ r->_errno = EBADF;
+ return -1;
+ }
+
+ // cache-aligned, cache-line-sized
+ __attribute__((aligned(0x40))) uint8_t alignedBuffer[0x40];
+
+ auto *deviceData = (FSADeviceData *) r->deviceData;
+
+ std::lock_guard lock(deviceData->mutex);
+
+ size_t bytesWritten = 0;
+ while (bytesWritten < len) {
+ // only use input buffer if cache-aligned and write size is a multiple of cache line size
+ // otherwise write from alignedBuffer
+ uint8_t *tmp = (uint8_t *) ptr;
+ size_t size = len - bytesWritten;
+
+ if ((uintptr_t) ptr & 0x3F) {
+ // write partial cache-line front-end
+ tmp = alignedBuffer;
+ size = MIN(size, 0x40 - ((uintptr_t) ptr & 0x3F));
+ } else if (size < 0x40) {
+ // write partial cache-line back-end
+ tmp = alignedBuffer;
+ } else {
+ // write whole cache lines
+ size &= ~0x3F;
+ }
+
+ // Limit each request to 256 KiB
+ if (size > 0x40000) {
+ size = 0x40000;
+ }
+
+ if (tmp == alignedBuffer) {
+ memcpy(tmp, ptr, size);
+ }
+
+ FSError status = FSAWriteFile(deviceData->clientHandle, tmp, 1, size, file->fd, 0);
+ if (status < 0) {
+ DEBUG_FUNCTION_LINE_ERR("FSAWriteFile(0x%08X, 0x%08X, 1, 0x%08X, 0x%08X, 0) failed: %s", deviceData->clientHandle, tmp, size, file->fd, FSAGetStatusStr(status));
+ if (bytesWritten != 0) {
+ return bytesWritten; // error after partial write
+ }
+
+ r->_errno = __fsa_translate_error(status);
+ return -1;
+ }
+
+ bytesWritten += status;
+ ptr += status;
+
+ if ((size_t) status != size) {
+ return bytesWritten; // partial write
+ }
+ }
+
+ return bytesWritten;
+}
diff --git a/source/fsa.cpp b/source/fsa.cpp
index 58890f2..e273f40 100644
--- a/source/fsa.cpp
+++ b/source/fsa.cpp
@@ -1,4 +1,5 @@
#include "mocha/fsa.h"
+#include "logger.h"
#include "utils.h"
#include
#include
@@ -103,10 +104,10 @@ FSError FSAEx_RawReadEx(int clientHandle, void *data, uint32_t size_bytes, uint3
if ((uint32_t) data & 0x3F) {
auto *alignedBuffer = memalign(0x40, ROUNDUP(size_bytes * cnt, 0x40));
if (!alignedBuffer) {
- OSReport("## ERROR: FSAEx_RawReadEx buffer not aligned (%08X).\n", data);
+ DEBUG_FUNCTION_LINE_ERR("FSAEx_RawReadEx buffer not aligned (%08X).\n", data);
return FS_ERROR_INVALID_ALIGNMENT;
}
- OSReport("## WARNING: FSAEx_RawReadEx buffer not aligned (%08X). Align to 0x40 for best performance\n", data);
+ DEBUG_FUNCTION_LINE_WARN("FSAEx_RawReadEx buffer not aligned (%08X). Align to 0x40 for best performance\n", data);
tmp = alignedBuffer;
}
diff --git a/source/utils.cpp b/source/utils.cpp
index 11f0828..4b6df26 100644
--- a/source/utils.cpp
+++ b/source/utils.cpp
@@ -33,6 +33,10 @@ const char *Mocha_GetStatusStr(MochaUtilsStatus status) {
return "MOCHA_RESULT_MAX_CLIENT";
case MOCHA_RESULT_OUT_OF_MEMORY:
return "MOCHA_RESULT_OUT_OF_MEMORY";
+ case MOCHA_RESULT_ALREADY_EXISTS:
+ return "MOCHA_RESULT_ALREADY_EXISTS";
+ case MOCHA_RESULT_ADD_DEVOPTAB_FAILED:
+ return "MOCHA_RESULT_ADD_DEVOPTAB_FAILED";
case MOCHA_RESULT_NOT_FOUND:
return "MOCHA_RESULT_NOT_FOUND";
case MOCHA_RESULT_UNSUPPORTED_API_VERSION: