From 25f2839c756f609ab0dac5df371f4c98a211e7da Mon Sep 17 00:00:00 2001 From: yellows8 Date: Thu, 23 Nov 2017 15:00:56 -0500 Subject: [PATCH] Imported util/utf from libctru. Updated appInit/appExit. Fs adjustments + implemented IDirectory. Imported a modified version of sdmc_dev from libctru as fs_dev, this currently isn't usable. --- nx/Makefile | 2 +- nx/include/switch.h | 3 + nx/include/switch/devices/fs_dev.h | 17 + nx/include/switch/services/fs.h | 39 +- nx/include/switch/util/utf.h | 156 +++ nx/source/devices/fs_dev.c | 1436 +++++++++++++++++++++++++++ nx/source/services/fs.c | 86 +- nx/source/system/init.c | 8 +- nx/source/util/utf/decode_utf16.c | 26 + nx/source/util/utf/decode_utf8.c | 88 ++ nx/source/util/utf/encode_utf16.c | 24 + nx/source/util/utf/encode_utf8.c | 45 + nx/source/util/utf/utf16_to_utf32.c | 37 + nx/source/util/utf/utf16_to_utf8.c | 50 + nx/source/util/utf/utf32_to_utf16.c | 36 + nx/source/util/utf/utf32_to_utf8.c | 40 + nx/source/util/utf/utf8_to_utf16.c | 46 + nx/source/util/utf/utf8_to_utf32.c | 37 + 18 files changed, 2166 insertions(+), 10 deletions(-) create mode 100644 nx/include/switch/devices/fs_dev.h create mode 100644 nx/include/switch/util/utf.h create mode 100644 nx/source/devices/fs_dev.c create mode 100644 nx/source/util/utf/decode_utf16.c create mode 100644 nx/source/util/utf/decode_utf8.c create mode 100644 nx/source/util/utf/encode_utf16.c create mode 100644 nx/source/util/utf/encode_utf8.c create mode 100644 nx/source/util/utf/utf16_to_utf32.c create mode 100644 nx/source/util/utf/utf16_to_utf8.c create mode 100644 nx/source/util/utf/utf32_to_utf16.c create mode 100644 nx/source/util/utf/utf32_to_utf8.c create mode 100644 nx/source/util/utf/utf8_to_utf16.c create mode 100644 nx/source/util/utf/utf8_to_utf32.c diff --git a/nx/Makefile b/nx/Makefile index e8ec3f88..85e1990a 100644 --- a/nx/Makefile +++ b/nx/Makefile @@ -24,7 +24,7 @@ VERSION := $(LIBNX_MAJOR).$(LIBNX_MINOR).$(LIBNX_PATCH) #--------------------------------------------------------------------------------- TARGET := nx #BUILD := build -SOURCES := source/arm source/system source/kernel source/services source/gfx source/devices +SOURCES := source/arm source/system source/kernel source/services source/gfx source/devices source/util/utf DATA := data INCLUDES := include diff --git a/nx/include/switch.h b/nx/include/switch.h index 9cfb5754..448f1513 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -21,6 +21,8 @@ extern "C" { #include #include +#include + #include #include #include @@ -38,6 +40,7 @@ extern "C" { #include #include +#include #ifdef __cplusplus } diff --git a/nx/include/switch/devices/fs_dev.h b/nx/include/switch/devices/fs_dev.h new file mode 100644 index 00000000..e2f08473 --- /dev/null +++ b/nx/include/switch/devices/fs_dev.h @@ -0,0 +1,17 @@ +/** + * @file fs_dev.h + * @brief FS driver. + */ +#pragma once + +//NOTE: This is currently not usable. + +/// Initializes the FS driver. +Result fsdevInit(void); + +/// Enable/disable copy in fsdev_write +void fsdevWriteSafe(bool enable); + +/// Exits the FS driver. +Result fsdevExit(void); + diff --git a/nx/include/switch/services/fs.h b/nx/include/switch/services/fs.h index 228e10ab..6440c485 100644 --- a/nx/include/switch/services/fs.h +++ b/nx/include/switch/services/fs.h @@ -2,6 +2,8 @@ // We use wrapped handles for type safety. +#define FS_MAX_PATH 0x301 + typedef struct { Handle h; } FsFileSystem; @@ -18,12 +20,39 @@ typedef struct { Handle h; } FsStorage; +/// Directory entry. +typedef struct +{ + char name[FS_MAX_PATH]; ///< Entry name. + u8 pad[3]; + u32 attributes; ///< Attributes. + u64 fileSize; ///< File size. +} FsDirectoryEntry; + typedef enum { ENTRYTYPE_FILE=0, ENTRYTYPE_DIR =1 } FsEntryType; -#define FS_MAX_PATH 0x301 +typedef enum +{ + FS_OPEN_READ = BIT(0), ///< Open for reading. + FS_OPEN_WRITE = BIT(1), ///< Open for writing. + FS_OPEN_APPEND = BIT(2), ///< Append file. +} FsFileFlags; + +/// For use with fsFsOpenDirectory. +typedef enum +{ + FS_DIROPEN_DIRECTORY = BIT(0), ///< Enable reading directory entries. + FS_DIROPEN_FILE = BIT(1), ///< Enable reading file entries. +} FsDirectoryFlags; + +/// Attribute flags. +typedef enum +{ + FS_ATTRIBUTE_FILE = BIT(0), ///< File. +} FsAttribute; Result fsInitialize(); void fsExit(void); @@ -51,11 +80,15 @@ void fsFsClose(FsFileSystem* fs); // IFile Result fsFileRead(FsFile* f, u64 off, void* buf, size_t len, size_t* out); -Result fsFileWrite(FsFile* f, u64 off, void* buf, size_t len, size_t* out); +Result fsFileWrite(FsFile* f, u64 off, const void* buf, size_t len, size_t* out); Result fsFileFlush(FsFile* f); Result fsFileSetSize(FsFile* f, u64 sz); Result fsFileGetSize(FsFile* f, u64* out); void fsFileClose(FsFile* f); -// todo: IDirectory +// IDirectory +Result fsDirRead(FsDir* d, u64 inval, size_t* total_entries, size_t max_entries, FsDirectoryEntry *buf); +Result fsDirGetEntryCount(FsDir* d, u64* count); +void fsDirClose(FsDir* d); + // todo: IStorage diff --git a/nx/include/switch/util/utf.h b/nx/include/switch/util/utf.h new file mode 100644 index 00000000..8c4f914b --- /dev/null +++ b/nx/include/switch/util/utf.h @@ -0,0 +1,156 @@ +/** + * @file utf.h + * @brief UTF conversion functions. + */ +#pragma once + +#include +#include + +/** Convert a UTF-8 sequence into a UTF-32 codepoint + * + * @param[out] out Output codepoint + * @param[in] in Input sequence + * + * @returns number of input code units consumed + * @returns -1 for error + */ +ssize_t decode_utf8 (uint32_t *out, const uint8_t *in); + +/** Convert a UTF-16 sequence into a UTF-32 codepoint + * + * @param[out] out Output codepoint + * @param[in] in Input sequence + * + * @returns number of input code units consumed + * @returns -1 for error + */ +ssize_t decode_utf16(uint32_t *out, const uint16_t *in); + +/** Convert a UTF-32 codepoint into a UTF-8 sequence + * + * @param[out] out Output sequence + * @param[in] in Input codepoint + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out must be able to store 4 code units + */ +ssize_t encode_utf8 (uint8_t *out, uint32_t in); + +/** Convert a UTF-32 codepoint into a UTF-16 sequence + * + * @param[out] out Output sequence + * @param[in] in Input codepoint + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out must be able to store 2 code units + */ +ssize_t encode_utf16(uint16_t *out, uint32_t in); + +/** Convert a UTF-8 sequence into a UTF-16 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf8_to_utf16(uint16_t *out, const uint8_t *in, size_t len); + +/** Convert a UTF-8 sequence into a UTF-32 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf8_to_utf32(uint32_t *out, const uint8_t *in, size_t len); + +/** Convert a UTF-16 sequence into a UTF-8 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf16_to_utf8(uint8_t *out, const uint16_t *in, size_t len); + +/** Convert a UTF-16 sequence into a UTF-32 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf16_to_utf32(uint32_t *out, const uint16_t *in, size_t len); + +/** Convert a UTF-32 sequence into a UTF-8 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf32_to_utf8(uint8_t *out, const uint32_t *in, size_t len); + +/** Convert a UTF-32 sequence into a UTF-16 sequence + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf32_to_utf16(uint16_t *out, const uint32_t *in, size_t len); + diff --git a/nx/source/devices/fs_dev.c b/nx/source/devices/fs_dev.c new file mode 100644 index 00000000..f6a1ffaa --- /dev/null +++ b/nx/source/devices/fs_dev.c @@ -0,0 +1,1436 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/*! @internal + * + * @file fsdev_dev.c + * + * FS Device + */ + +static int fsdev_translate_error(Result error); + +static int fsdev_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode); +static int fsdev_close(struct _reent *r, void *fd); +static ssize_t fsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len); +static ssize_t fsdev_write_safe(struct _reent *r, void *fd, const char *ptr, size_t len); +static ssize_t fsdev_read(struct _reent *r, void *fd, char *ptr, size_t len); +static off_t fsdev_seek(struct _reent *r, void *fd, off_t pos, int dir); +static int fsdev_fstat(struct _reent *r, void *fd, struct stat *st); +static int fsdev_stat(struct _reent *r, const char *file, struct stat *st); +static int fsdev_link(struct _reent *r, const char *existing, const char *newLink); +static int fsdev_unlink(struct _reent *r, const char *name); +static int fsdev_chdir(struct _reent *r, const char *name); +static int fsdev_rename(struct _reent *r, const char *oldName, const char *newName); +static int fsdev_mkdir(struct _reent *r, const char *path, int mode); +static DIR_ITER* fsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path); +static int fsdev_dirreset(struct _reent *r, DIR_ITER *dirState); +static int fsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat); +static int fsdev_dirclose(struct _reent *r, DIR_ITER *dirState); +static int fsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf); +static int fsdev_ftruncate(struct _reent *r, void *fd, off_t len); +static int fsdev_fsync(struct _reent *r, void *fd); +static int fsdev_chmod(struct _reent *r, const char *path, mode_t mode); +static int fsdev_fchmod(struct _reent *r, void *fd, mode_t mode); +static int fsdev_rmdir(struct _reent *r, const char *name); + +/*! @cond INTERNAL */ + +/*! Open file struct */ +typedef struct +{ + FsFile fd; + int flags; /*! Flags used in open(2) */ + u64 offset; /*! Current file offset */ +} fsdev_file_t; + +#define FSDEV_DIRITER_MAGIC 0x66736476 /* "fsdv" */ + +/*! Open directory struct */ +typedef struct +{ + u32 magic; /*! "fsdv" */ + FsDir fd; + ssize_t index; /*! Current entry index */ + size_t size; /*! Current batch size */ + FsDirectoryEntry entry_data[32]; /*! Temporary storage for reading entries */ +} fsdev_dir_t; + +/*! fsdev devoptab */ +static devoptab_t +fsdev_devoptab = +{ + .structSize = sizeof(fsdev_file_t), + .open_r = fsdev_open, + .close_r = fsdev_close, + .write_r = fsdev_write, + .read_r = fsdev_read, + .seek_r = fsdev_seek, + .fstat_r = fsdev_fstat, + .stat_r = fsdev_stat, + .link_r = fsdev_link, + .unlink_r = fsdev_unlink, + .chdir_r = fsdev_chdir, + .rename_r = fsdev_rename, + .mkdir_r = fsdev_mkdir, + .dirStateSize = sizeof(fsdev_dir_t), + .diropen_r = fsdev_diropen, + .dirreset_r = fsdev_dirreset, + .dirnext_r = fsdev_dirnext, + .dirclose_r = fsdev_dirclose, + .statvfs_r = fsdev_statvfs, + .ftruncate_r = fsdev_ftruncate, + .fsync_r = fsdev_fsync, + .deviceData = 0, + .chmod_r = fsdev_chmod, + .fchmod_r = fsdev_fchmod, + .rmdir_r = fsdev_rmdir, +}; + +typedef struct +{ + bool setup; + devoptab_t device; + FsFileSystem fs; + char name[32]; +} fsdev_fsdevice; + +static fsdev_fsdevice fsdev_fsdevices[32]; + +/*! @endcond */ + +static char __cwd[PATH_MAX+1] = "/"; +static __thread char __fixedpath[PATH_MAX+1]; +//static __thread uint16_t __utf16path[PATH_MAX+1]; + +static fsdev_fsdevice *fsdevFindDevice(const char *name) +{ + u32 i; + u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice); + fsdev_fsdevice *device = NULL; + + for(i=0; isetup) + return device; + } + else if(device->setup) //Find the device with the input name. + { + if(strncmp(device->name, name, strlen(device->name))==0) + return device; + } + } + + return NULL; +} + +static const char* +fsdev_fixpath(struct _reent *r, + const char *path, + fsdev_fsdevice **device) +{ + ssize_t units; + uint32_t code; + const uint8_t *p = (const uint8_t*)path; + + if(device) + *device = fsdevFindDevice(path); + + // Move the path pointer to the start of the actual path + do + { + units = decode_utf8(&code, p); + if(units < 0) + { + r->_errno = EILSEQ; + return NULL; + } + + p += units; + } while(code != ':' && code != 0); + + // We found a colon; p points to the actual path + if(code == ':') + path = (const char*)p; + + // Make sure there are no more colons and that the + // remainder of the filename is valid UTF-8 + p = (const uint8_t*)path; + do + { + units = decode_utf8(&code, p); + if(units < 0) + { + r->_errno = EILSEQ; + return NULL; + } + + if(code == ':') + { + r->_errno = EINVAL; + return NULL; + } + + p += units; + } while(code != 0); + + if(path[0] == '/') + strncpy(__fixedpath, path, PATH_MAX); + else + { + strncpy(__fixedpath, __cwd, PATH_MAX); + strncat(__fixedpath, path, PATH_MAX); + } + + if(__fixedpath[PATH_MAX] != 0) + { + __fixedpath[PATH_MAX] = 0; + r->_errno = ENAMETOOLONG; + return NULL; + } + + return __fixedpath; +} + +static int +fsdev_getfspath(struct _reent *r, + const char *path, + fsdev_fsdevice **device, + char *outpath) +{ + //ssize_t units; + + if(fsdev_fixpath(r, path, device) == NULL) + return -1; + + //TODO: What encoding does FS paths use? + + /*units = utf8_to_utf16(__utf16path, (const uint8_t*)__fixedpath, PATH_MAX); + if(units < 0) + { + r->_errno = EILSEQ; + return fspath; + } + if(units >= PATH_MAX) + { + r->_errno = ENAMETOOLONG; + return fspath; + } + + __utf16path[units] = 0;*/ + + memset(outpath, 0, FS_MAX_PATH); + strncpy(outpath, __fixedpath, FS_MAX_PATH); + + return 0; +} + +static ssize_t fsdev_convertfromfspath(uint8_t *out, uint8_t *in, size_t len) +{ + //TODO: What encoding does FS paths use? + + strncpy((char*)out, (char*)in, len); + return strnlen((char*)out, len); +} + +extern int __system_argc; +extern char** __system_argv; + +static bool fsdevInitialised = false; + +static void fsdevUpdateDevices(void) +{ + u32 i; + u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice); + + for(i=0; ifs = fs; + memset(device->name, 0, sizeof(device->name)); + strncpy(device->name, name, sizeof(device->name)-1); + + int dev = AddDevice(&device->device); + if(dev==-1) + { + fsFsClose(&device->fs); + return dev; + } + + device->setup = 1; + + return dev; +} + +static int _fsdevUnmountDeviceStruct(fsdev_fsdevice *device) +{ + if(!device->setup) + return 0; + + RemoveDevice(device->name); + fsFsClose(&device->fs); + + memset(device, 0, sizeof(fsdev_fsdevice)); + + return 0; +} + +int fsdevUnmountDevice(const char *name) +{ + fsdev_fsdevice *device; + + device = fsdevFindDevice(name); + if(device==NULL) + return -1; + + return _fsdevUnmountDeviceStruct(device); +} + +/*! Initialize SDMC device */ +Result fsdevInit(void) +{ + /*ssize_t units; + uint32_t code; + char *p;*/ + Result rc = 0; + FsFileSystem fs; + + if(fsdevInitialised) + return rc; + + memset(fsdev_fsdevices, 0, sizeof(fsdev_fsdevices)); + fsdevUpdateDevices(); + + rc = fsMountSdcard(&fs); + if(R_SUCCEEDED(rc)) + { + int dev = fsdevMountDevice("sdmc", fs); + + if(dev != -1) + { + setDefaultDevice(dev); + //TODO: Re-enable this once __system_argc/__system_argv are actually defined. + /*if(__system_argc != 0 && __system_argv[0] != NULL) + { + if(FindDevice(__system_argv[0]) == dev) + { + strncpy(__fixedpath,__system_argv[0],PATH_MAX); + if(__fixedpath[PATH_MAX] != 0) + { + __fixedpath[PATH_MAX] = 0; + } + else + { + char *last_slash = NULL; + p = __fixedpath; + do + { + units = decode_utf8(&code, (const uint8_t*)p); + if(units < 0) + { + last_slash = NULL; + break; + } + + if(code == '/') + last_slash = p; + + p += units; + } while(code != 0); + + if(last_slash != NULL) + { + last_slash[0] = 0; + chdir(__fixedpath); + } + } + } + }*/ + } + } + + fsdevInitialised = true; + + return rc; +} + +/*! Enable/disable safe fsdev_write + * + * Safe fsdev_write is enabled by default. If it is disabled, you will be + * unable to write from read-only buffers. + * + * @param[in] enable Whether to enable + */ +void fsdevWriteSafe(bool enable) +{ + if(enable) + fsdev_devoptab.write_r = fsdev_write_safe; + else + fsdev_devoptab.write_r = fsdev_write; + + fsdevUpdateDevices(); +} + +/*! Clean up fsdev devices */ +Result fsdevExit(void) +{ + u32 i; + u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice); + Result rc=0; + + if(!fsdevInitialised) return rc; + + for(i=0; i_errno = EINVAL; + return -1; + } + break; + + /* write-only */ + case O_WRONLY: + fsdev_flags |= FS_OPEN_WRITE; + break; + + /* read and write */ + case O_RDWR: + fsdev_flags |= (FS_OPEN_READ | FS_OPEN_WRITE); + break; + + /* an invalid option was supplied */ + default: + r->_errno = EINVAL; + return -1; + } + + /* Test O_EXCL. */ + if((flags & O_CREAT) && (flags & O_EXCL)) + { + rc = fsFsCreateFile(&device->fs, fs_path, 0, attributes); + if(R_FAILED(rc)) + { + r->_errno = fsdev_translate_error(rc); + return -1; + } + } + + /* set attributes */ + /*if(!(mode & S_IWUSR)) + attributes |= FS_ATTRIBUTE_READONLY;*/ + + /* open the file */ + rc = fsFsOpenFile(&device->fs, fs_path, fsdev_flags, &fd); + if(R_SUCCEEDED(rc)) + { + if((flags & O_ACCMODE) != O_RDONLY && (flags & O_TRUNC)) + { + rc = fsFileSetSize(&fd, 0); + if(R_FAILED(rc)) + { + fsFileClose(&fd); + r->_errno = fsdev_translate_error(rc); + return -1; + } + } + + file->fd = fd; + file->flags = (flags & (O_ACCMODE|O_APPEND|O_SYNC)); + file->offset = 0; + return 0; + } + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Close an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to fsdev_file_t + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_close(struct _reent *r, + void *fd) +{ + Result rc=0; + + /* get pointer to our data */ + fsdev_file_t *file = (fsdev_file_t*)fd; + + fsFileClose(&file->fd); + if(R_SUCCEEDED(rc)) + return 0; + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Write to an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in,out] fd Pointer to fsdev_file_t + * @param[in] ptr Pointer to data to write + * @param[in] len Length of data to write + * + * @returns number of bytes written + * @returns -1 for error + */ +static ssize_t +fsdev_write(struct _reent *r, + void *fd, + const char *ptr, + size_t len) +{ + Result rc; + size_t bytes; + + /* get pointer to our data */ + fsdev_file_t *file = (fsdev_file_t*)fd; + + /* check that the file was opened with write access */ + if((file->flags & O_ACCMODE) == O_RDONLY) + { + r->_errno = EBADF; + return -1; + } + + if(file->flags & O_APPEND) + { + /* append means write from the end of the file */ + rc = fsFileGetSize(&file->fd, &file->offset); + if(R_FAILED(rc)) + { + r->_errno = fsdev_translate_error(rc); + return -1; + } + } + + rc = fsFileWrite(&file->fd, file->offset, ptr, len, &bytes); + if(R_FAILED(rc)) + { + r->_errno = fsdev_translate_error(rc); + return -1; + } + + file->offset += bytes; + + /* check if this is synchronous or not */ + if(file->flags & O_SYNC) + fsFileFlush(&file->fd); + + return bytes; +} + +/*! Write to an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in,out] fd Pointer to fsdev_file_t + * @param[in] ptr Pointer to data to write + * @param[in] len Length of data to write + * + * @returns number of bytes written + * @returns -1 for error + */ +static ssize_t +fsdev_write_safe(struct _reent *r, + void *fd, + const char *ptr, + size_t len) +{ + Result rc; + size_t bytes, bytesWritten = 0; + + /* get pointer to our data */ + fsdev_file_t *file = (fsdev_file_t*)fd; + + /* check that the file was opened with write access */ + if((file->flags & O_ACCMODE) == O_RDONLY) + { + r->_errno = EBADF; + return -1; + } + + if(file->flags & O_APPEND) + { + /* append means write from the end of the file */ + rc = fsFileGetSize(&file->fd, &file->offset); + if(R_FAILED(rc)) + { + r->_errno = fsdev_translate_error(rc); + return -1; + } + } + + /* Copy to internal buffer and write in chunks. + * You cannot write from read-only memory. + */ + static __thread char tmp_buffer[8192]; + while(len > 0) + { + size_t toWrite = len; + if(toWrite > sizeof(tmp_buffer)) + toWrite = sizeof(tmp_buffer); + + /* copy to internal buffer */ + memcpy(tmp_buffer, ptr, toWrite); + + /* write the data */ + rc = fsFileWrite(&file->fd, file->offset, tmp_buffer, toWrite, &bytes); + + if(R_FAILED(rc)) + { + /* return partial transfer */ + if(bytesWritten > 0) + return bytesWritten; + + r->_errno = fsdev_translate_error(rc); + return -1; + } + + /* check if this is synchronous or not */ + if(file->flags & O_SYNC) + fsFileFlush(&file->fd); + + file->offset += bytes; + bytesWritten += bytes; + ptr += bytes; + len -= bytes; + } + + return bytesWritten; +} + +/*! Read from an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in,out] fd Pointer to fsdev_file_t + * @param[out] ptr Pointer to buffer to read into + * @param[in] len Length of data to read + * + * @returns number of bytes read + * @returns -1 for error + */ +static ssize_t +fsdev_read(struct _reent *r, + void *fd, + char *ptr, + size_t len) +{ + Result rc; + size_t bytes; + + /* get pointer to our data */ + fsdev_file_t *file = (fsdev_file_t*)fd; + + /* check that the file was opened with read access */ + if((file->flags & O_ACCMODE) == O_WRONLY) + { + r->_errno = EBADF; + return -1; + } + + /* read the data */ + rc = fsFileRead(&file->fd, file->offset, ptr, len, &bytes); + if(R_SUCCEEDED(rc)) + { + /* update current file offset */ + file->offset += bytes; + return (ssize_t)bytes; + } + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Update an open file's current offset + * + * @param[in,out] r newlib reentrancy struct + * @param[in,out] fd Pointer to fsdev_file_t + * @param[in] pos Offset to seek to + * @param[in] whence Where to seek from + * + * @returns new offset for success + * @returns -1 for error + */ +static off_t +fsdev_seek(struct _reent *r, + void *fd, + off_t pos, + int whence) +{ + Result rc; + u64 offset; + + /* get pointer to our data */ + fsdev_file_t *file = (fsdev_file_t*)fd; + + /* 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: + rc = fsFileGetSize(&file->fd, &offset); + if(R_FAILED(rc)) + { + r->_errno = fsdev_translate_error(rc); + return -1; + } + break; + + /* an invalid option was provided */ + default: + r->_errno = EINVAL; + return -1; + } + + /* TODO: A better check that prevents overflow. */ + if(pos < 0 && offset < -pos) + { + /* don't allow seek to before the beginning of the file */ + r->_errno = EINVAL; + return -1; + } + + /* update the current offset */ + file->offset = offset + pos; + return file->offset; +} + +/*! Get file stats from an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to fsdev_file_t + * @param[out] st Pointer to file stats to fill + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_fstat(struct _reent *r, + void *fd, + struct stat *st) +{ + Result rc; + u64 size; + fsdev_file_t *file = (fsdev_file_t*)fd; + + rc = fsFileGetSize(&file->fd, &size); + if(R_SUCCEEDED(rc)) + { + memset(st, 0, sizeof(struct stat)); + st->st_size = (off_t)size; + st->st_nlink = 1; + st->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + return 0; + } + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Get file stats + * + * @param[in,out] r newlib reentrancy struct + * @param[in] file Path to file + * @param[out] st Pointer to file stats to fill + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_stat(struct _reent *r, + const char *file, + struct stat *st) +{ + FsFile fd; + FsDir fdir; + Result rc; + char fs_path[FS_MAX_PATH]; + fsdev_fsdevice *device = NULL; + + if(fsdev_getfspath(r, file, &device, fs_path)==-1) + return -1; + + if(R_SUCCEEDED(rc = fsFsOpenFile(&device->fs, fs_path, FS_OPEN_READ, &fd))) + { + fsdev_file_t tmpfd = { .fd = fd }; + rc = fsdev_fstat(r, &tmpfd, st); + fsFileClose(&fd); + + return rc; + } + else if(R_SUCCEEDED(rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fdir))) + { + memset(st, 0, sizeof(struct stat)); + st->st_nlink = 1; + st->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; + fsDirClose(&fdir); + return 0; + } + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Hard link a file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] existing Path of file to link + * @param[in] newLink Path of new link + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_link(struct _reent *r, + const char *existing, + const char *newLink) +{ + r->_errno = ENOSYS; + return -1; +} + +/*! Unlink a file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] name Path of file to unlink + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_unlink(struct _reent *r, + const char *name) +{ + Result rc; + char fs_path[FS_MAX_PATH]; + fsdev_fsdevice *device = NULL; + + if(fsdev_getfspath(r, name, &device, fs_path)==-1) + return -1; + + rc = fsFsDeleteFile(&device->fs, fs_path); + if(R_SUCCEEDED(rc)) + return 0; + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Change current working directory + * + * @param[in,out] r newlib reentrancy struct + * @param[in] name Path to new working directory + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_chdir(struct _reent *r, + const char *name) +{ + FsDir fd; + Result rc; + char fs_path[FS_MAX_PATH]; + fsdev_fsdevice *device = NULL; + + if(fsdev_getfspath(r, name, &device, fs_path)==-1) + return -1; + + rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fd); + if(R_SUCCEEDED(rc)) + { + fsDirClose(&fd); + strncpy(__cwd, __fixedpath, PATH_MAX); + return 0; + } + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Rename a file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] oldName Path to rename from + * @param[in] newName Path to rename to + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_rename(struct _reent *r, + const char *oldName, + const char *newName) +{ + Result rc; + fsdev_fsdevice *device = NULL; + char fs_path_old[FS_MAX_PATH]; + char fs_path_new[FS_MAX_PATH]; + + if(fsdev_getfspath(r, oldName, &device, fs_path_old)==-1) + return -1; + + if(fsdev_getfspath(r, newName, NULL, fs_path_new)==-1) + return -1; + + rc = fsFsRenameFile(&device->fs, fs_path_old, fs_path_new); + if(R_SUCCEEDED(rc)) + return 0; + + rc = fsFsRenameDirectory(&device->fs, fs_path_old, fs_path_new); + if(R_SUCCEEDED(rc)) + return 0; + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Create a directory + * + * @param[in,out] r newlib reentrancy struct + * @param[in] path Path of directory to create + * @param[in] mode Permissions of created directory + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_mkdir(struct _reent *r, + const char *path, + int mode) +{ + Result rc; + char fs_path[FS_MAX_PATH]; + fsdev_fsdevice *device = NULL; + + if(fsdev_getfspath(r, path, &device, fs_path)==-1) + return -1; + + rc = fsFsCreateDirectory(&device->fs, fs_path); + if(R_SUCCEEDED(rc)) + return 0; + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Open a directory + * + * @param[in,out] r newlib reentrancy struct + * @param[in] dirState Pointer to open directory state + * @param[in] path Path of directory to open + * + * @returns dirState for success + * @returns NULL for error + */ +static DIR_ITER* +fsdev_diropen(struct _reent *r, + DIR_ITER *dirState, + const char *path) +{ + FsDir fd; + Result rc; + char fs_path[FS_MAX_PATH]; + fsdev_fsdevice *device = NULL; + + if(fsdev_getfspath(r, path, &device, fs_path)==-1) + return NULL; + + /* get pointer to our data */ + fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct); + + /* open the directory */ + rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fd); + if(R_SUCCEEDED(rc)) + { + dir->magic = FSDEV_DIRITER_MAGIC; + dir->fd = fd; + dir->index = -1; + dir->size = 0; + memset(&dir->entry_data, 0, sizeof(dir->entry_data)); + return dirState; + } + + r->_errno = fsdev_translate_error(rc); + return NULL; +} + +/*! Reset an open directory to its intial state + * + * @param[in,out] r newlib reentrancy struct + * @param[in] dirState Pointer to open directory state + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_dirreset(struct _reent *r, + DIR_ITER *dirState) +{ + r->_errno = ENOSYS; + return -1; +} + +/*! Fetch the next entry of an open directory + * + * @param[in,out] r newlib reentrancy struct + * @param[in] dirState Pointer to open directory state + * @param[out] filename Buffer to store entry name + * @param[out] filestat Buffer to store entry attributes + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_dirnext(struct _reent *r, + DIR_ITER *dirState, + char *filename, + struct stat *filestat) +{ + Result rc; + size_t entries; + ssize_t units; + FsDirectoryEntry *entry; + + /* get pointer to our data */ + fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct); + + static const size_t max_entries = sizeof(dir->entry_data) / sizeof(dir->entry_data[0]); + + /* check if it's in the batch already */ + if(++dir->index < dir->size) + { + rc = 0; + } + else + { + /* reset batch info */ + dir->index = -1; + dir->size = 0; + + /* fetch the next batch */ + memset(dir->entry_data, 0, sizeof(dir->entry_data)); + rc = fsDirRead(&dir->fd, 0, &entries, max_entries, dir->entry_data); + if(R_SUCCEEDED(rc)) + { + if(entries == 0) + { + /* there are no more entries; ENOENT signals end-of-directory */ + r->_errno = ENOENT; + return -1; + } + + dir->index = 0; + dir->size = entries; + } + } + + if(R_SUCCEEDED(rc)) + { + entry = &dir->entry_data[dir->index]; + + /* fill in the stat info */ + filestat->st_ino = 0; + if(entry->attributes & FS_ATTRIBUTE_FILE) + filestat->st_mode = S_IFREG; + else + filestat->st_mode = S_IFDIR; + + /* convert name from UTF-16 to UTF-8 */ + memset(filename, 0, NAME_MAX); + units = fsdev_convertfromfspath((uint8_t*)filename, (uint8_t*)entry->name, NAME_MAX); + if(units < 0) + { + r->_errno = EILSEQ; + return -1; + } + + if(units >= NAME_MAX) + { + r->_errno = ENAMETOOLONG; + return -1; + } + + return 0; + } + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Close an open directory + * + * @param[in,out] r newlib reentrancy struct + * @param[in] dirState Pointer to open directory state + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_dirclose(struct _reent *r, + DIR_ITER *dirState) +{ + Result rc=0; + + /* get pointer to our data */ + fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct); + + /* close the directory */ + fsDirClose(&dir->fd); + if(R_SUCCEEDED(rc)) + return 0; + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Get filesystem statistics + * + * @param[in,out] r newlib reentrancy struct + * @param[in] path Path to filesystem to get statistics of + * @param[out] buf Buffer to fill + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_statvfs(struct _reent *r, + const char *path, + struct statvfs *buf) +{ + Result rc=0; + //FS_ArchiveResource resource; + //bool writable = false; + + //rc = FSUSER_GetSdmcArchiveResource(&resource); + + if(R_SUCCEEDED(rc)) + { + buf->f_bsize = 0;//resource.clusterSize; + buf->f_frsize = 0;//resource.clusterSize; + buf->f_blocks = 0;//resource.totalClusters; + buf->f_bfree = 0;//resource.freeClusters; + buf->f_bavail = 0;//resource.freeClusters; + buf->f_files = 0; //??? how to get + buf->f_ffree = 0;//resource.freeClusters; + buf->f_favail = 0;//resource.freeClusters; + buf->f_fsid = 0; //??? how to get + buf->f_flag = ST_NOSUID; + buf->f_namemax = 0; //??? how to get + + /*rc = FSUSER_IsSdmcWritable(&writable); + if(R_FAILED(rc) || !writable) + buf->f_flag |= ST_RDONLY;*/ + + return 0; + } + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Truncate an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to fsdev_file_t + * @param[in] len Length to truncate file to + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_ftruncate(struct _reent *r, + void *fd, + off_t len) +{ + Result rc; + + /* get pointer to our data */ + fsdev_file_t *file = (fsdev_file_t*)fd; + + /* make sure length is non-negative */ + if(len < 0) + { + r->_errno = EINVAL; + return -1; + } + + /* set the new file size */ + rc = fsFileSetSize(&file->fd, len); + if(R_SUCCEEDED(rc)) + return 0; + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Synchronize a file to media + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to fsdev_file_t + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_fsync(struct _reent *r, + void *fd) +{ + Result rc; + + /* get pointer to our data */ + fsdev_file_t *file = (fsdev_file_t*)fd; + + rc = fsFileFlush(&file->fd); + if(R_SUCCEEDED(rc)) + return 0; + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +/*! Change a file's mode + * + * @param[in,out] r newlib reentrancy struct + * @param[in] path Path to file to update + * @param[in] mode New mode to set + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_chmod(struct _reent *r, + const char *path, + mode_t mode) +{ + r->_errno = ENOSYS; + return -1; +} + +/*! Change an open file's mode + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to fsdev_file_t + * @param[in] mode New mode to set + * + * @returns 0 for success + * @returns -1 for failure + */ +static int +fsdev_fchmod(struct _reent *r, + void *fd, + mode_t mode) +{ + r->_errno = ENOSYS; + return -1; +} + +/*! Remove a directory + * + * @param[in,out] r newlib reentrancy struct + * @param[in] name Path of directory to remove + * + * @returns 0 for success + * @returns -1 for error + */ +static int +fsdev_rmdir(struct _reent *r, + const char *name) +{ + Result rc; + char fs_path[FS_MAX_PATH]; + fsdev_fsdevice *device = NULL; + + if(fsdev_getfspath(r, name, &device, fs_path)==-1) + return -1; + + rc = fsFsDeleteDirectory(&device->fs, fs_path); + if(R_SUCCEEDED(rc)) + return 0; + + r->_errno = fsdev_translate_error(rc); + return -1; +} + +Result +fsdev_getmtime(const char *name, + u64 *mtime) +{ + //Result rc; + struct _reent r; + + r._errno = ENOSYS; + + if(r._errno != 0) + errno = r._errno; + + return -1; + + /*r._errno = 0; + + fs_path = fsdev_getfspath(&r, name); + if(r._errno != 0) + errno = r._errno; + + if(fs_path.data == NULL) + return -1;*/ + + /*if(rc == 0) + {*/ + /* convert from milliseconds to seconds */ + //*mtime /= 1000; + /* convert from 2000-based timestamp to UNIX timestamp */ + /**mtime += 946684800; + } + + return rc;*/ + +} + +/*! Error map */ +typedef struct +{ + Result fs_error; //!< Error from FS service + int error; //!< POSIX errno +} error_map_t; + +/*! Error table */ +static const error_map_t error_table[] = +{ + /* keep this list sorted! */ + { 0x202, ENOENT, }, + { 0x2EE202, EINVAL, }, + { 0x2EE602, ENAMETOOLONG, }, +}; +static const size_t num_errors = sizeof(error_table)/sizeof(error_table[0]); + +/*! Comparison function for bsearch on error_table + * + * @param[in] p1 Left side of comparison + * @param[in] p2 Right side of comparison + * + * @returns <0 if lhs < rhs + * @returns >0 if lhs > rhs + * @returns 0 if lhs == rhs + */ +static int +error_cmp(const void *p1, const void *p2) +{ + const error_map_t *lhs = (const error_map_t*)p1; + const error_map_t *rhs = (const error_map_t*)p2; + + if((u32)lhs->fs_error < (u32)rhs->fs_error) + return -1; + else if((u32)lhs->fs_error > (u32)rhs->fs_error) + return 1; + return 0; +} + +/*! Translate FS service error to errno + * + * @param[in] error FS service error + * + * @returns errno + */ +static int +fsdev_translate_error(Result error) +{ + error_map_t key = { .fs_error = error }; + const error_map_t *rc = bsearch(&key, error_table, num_errors, + sizeof(error_map_t), error_cmp); + + if(rc != NULL) + return rc->error; + + return (int)error; +} + diff --git a/nx/source/services/fs.c b/nx/source/services/fs.c index 8a84ceaa..907857b7 100644 --- a/nx/source/services/fs.c +++ b/nx/source/services/fs.c @@ -254,7 +254,7 @@ Result fsFsDeleteDirectoryRecursively(FsFileSystem* fs, const char* path) { return rc; } -Result fsRenameFile(FsFileSystem* fs, const char* path0, const char* path1) { +Result fsFsRenameFile(FsFileSystem* fs, const char* path0, const char* path1) { IpcCommand c; ipcInitialize(&c); ipcAddSendStatic(&c, path0, FS_MAX_PATH, 0); @@ -287,7 +287,7 @@ Result fsRenameFile(FsFileSystem* fs, const char* path0, const char* path1) { return rc; } -Result fsRenameDirectory(FsFileSystem* fs, const char* path0, const char* path1) { +Result fsFsRenameDirectory(FsFileSystem* fs, const char* path0, const char* path1) { IpcCommand c; ipcInitialize(&c); ipcAddSendStatic(&c, path0, FS_MAX_PATH, 0); @@ -584,7 +584,7 @@ Result fsFileRead(FsFile* f, u64 off, void* buf, size_t len, size_t* out) { return rc; } -Result fsFileWrite(FsFile* f, u64 off, void* buf, size_t len, size_t* out) { +Result fsFileWrite(FsFile* f, u64 off, const void* buf, size_t len, size_t* out) { IpcCommand c; ipcInitialize(&c); ipcAddSendBuffer(&c, buf, len, 1); @@ -717,7 +717,8 @@ Result fsFileGetSize(FsFile* f, u64* out) { u64 size; } *resp = r.Raw; - rc = resp->size; + rc = resp->result; + if (R_SUCCEEDED(rc) && out) *out = resp->size; } return rc; @@ -727,3 +728,80 @@ void fsFileClose(FsFile* f) { svcCloseHandle(f->h); } +// IDirectory implementation +void fsDirClose(FsDir* d) { + svcCloseHandle(d->h); +} + +Result fsDirRead(FsDir* d, u64 inval, size_t* total_entries, size_t max_entries, FsDirectoryEntry *buf) { + IpcCommand c; + ipcInitialize(&c); + ipcAddRecvBuffer(&c, buf, sizeof(FsDirectoryEntry)*max_entries, 0); + + struct { + u64 magic; + u64 cmd_id; + u64 inval; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + raw->inval = inval; + + Result rc = ipcDispatch(d->h); + + if (R_SUCCEEDED(rc)) { + IpcCommandResponse r; + ipcParseResponse(&r); + + struct { + u64 magic; + u64 result; + u64 total_entries; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + if (total_entries) *total_entries = resp->total_entries; + } + } + + return rc; +} + +Result fsDirGetEntryCount(FsDir* d, u64* count) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 1; + + Result rc = ipcDispatch(d->h); + + if (R_SUCCEEDED(rc)) { + IpcCommandResponse r; + ipcParseResponse(&r); + + struct { + u64 magic; + u64 result; + u64 count; + } *resp = r.Raw; + + rc = resp->result; + if (R_SUCCEEDED(rc) && count) *count = resp->count; + } + + return rc; +} + diff --git a/nx/source/system/init.c b/nx/source/system/init.c index 717b7420..d3d1a57f 100644 --- a/nx/source/system/init.c +++ b/nx/source/system/init.c @@ -32,17 +32,21 @@ void __attribute__((weak)) __appInit(void) { // Initialize default services. smInitialize(); - fsInitialize(); appletInitialize(); hidInitialize(); + + fsInitialize(); + //fsdevInit(); } void __attribute__((weak)) __appExit(void) { // Cleanup default services. + //fsdevExit(); + fsExit(); + hidExit(); appletExit(); - fsExit(); smExit(); } diff --git a/nx/source/util/utf/decode_utf16.c b/nx/source/util/utf/decode_utf16.c new file mode 100644 index 00000000..086f10b8 --- /dev/null +++ b/nx/source/util/utf/decode_utf16.c @@ -0,0 +1,26 @@ +#include "switch/types.h" +#include "switch/util/utf.h" + +ssize_t +decode_utf16(uint32_t *out, + const uint16_t *in) +{ + uint16_t code1, code2; + + code1 = *in++; + if(code1 >= 0xD800 && code1 < 0xDC00) + { + /* surrogate pair */ + code2 = *in++; + if(code2 >= 0xDC00 && code2 < 0xE000) + { + *out = (code1 << 10) + code2 - 0x35FDC00; + return 2; + } + + return -1; + } + + *out = code1; + return 1; +} diff --git a/nx/source/util/utf/decode_utf8.c b/nx/source/util/utf/decode_utf8.c new file mode 100644 index 00000000..a4bd3f25 --- /dev/null +++ b/nx/source/util/utf/decode_utf8.c @@ -0,0 +1,88 @@ +#include "switch/util/utf.h" + +ssize_t +decode_utf8(uint32_t *out, + const uint8_t *in) +{ + uint8_t code1, code2, code3, code4; + + code1 = *in++; + if(code1 < 0x80) + { + /* 1-byte sequence */ + *out = code1; + return 1; + } + else if(code1 < 0xC2) + { + return -1; + } + else if(code1 < 0xE0) + { + /* 2-byte sequence */ + code2 = *in++; + if((code2 & 0xC0) != 0x80) + { + return -1; + } + + *out = (code1 << 6) + code2 - 0x3080; + return 2; + } + else if(code1 < 0xF0) + { + /* 3-byte sequence */ + code2 = *in++; + if((code2 & 0xC0) != 0x80) + { + return -1; + } + if(code1 == 0xE0 && code2 < 0xA0) + { + return -1; + } + + code3 = *in++; + if((code3 & 0xC0) != 0x80) + { + return -1; + } + + *out = (code1 << 12) + (code2 << 6) + code3 - 0xE2080; + return 3; + } + else if(code1 < 0xF5) + { + /* 4-byte sequence */ + code2 = *in++; + if((code2 & 0xC0) != 0x80) + { + return -1; + } + if(code1 == 0xF0 && code2 < 0x90) + { + return -1; + } + if(code1 == 0xF4 && code2 >= 0x90) + { + return -1; + } + + code3 = *in++; + if((code3 & 0xC0) != 0x80) + { + return -1; + } + + code4 = *in++; + if((code4 & 0xC0) != 0x80) + { + return -1; + } + + *out = (code1 << 18) + (code2 << 12) + (code3 << 6) + code4 - 0x3C82080; + return 4; + } + + return -1; +} diff --git a/nx/source/util/utf/encode_utf16.c b/nx/source/util/utf/encode_utf16.c new file mode 100644 index 00000000..4a56fc9f --- /dev/null +++ b/nx/source/util/utf/encode_utf16.c @@ -0,0 +1,24 @@ +#include "switch/util/utf.h" + +ssize_t +encode_utf16(uint16_t *out, + uint32_t in) +{ + if(in < 0x10000) + { + if(out != NULL) + *out++ = in; + return 1; + } + else if(in < 0x110000) + { + if(out != NULL) + { + *out++ = (in >> 10) + 0xD7C0; + *out++ = (in & 0x3FF) + 0xDC00; + } + return 2; + } + + return -1; +} diff --git a/nx/source/util/utf/encode_utf8.c b/nx/source/util/utf/encode_utf8.c new file mode 100644 index 00000000..611606c7 --- /dev/null +++ b/nx/source/util/utf/encode_utf8.c @@ -0,0 +1,45 @@ +#include "switch/util/utf.h" + +ssize_t +encode_utf8(uint8_t *out, + uint32_t in) +{ + if(in < 0x80) + { + if(out != NULL) + *out++ = in; + return 1; + } + else if(in < 0x800) + { + if(out != NULL) + { + *out++ = (in >> 6) + 0xC0; + *out++ = (in & 0x3F) + 0x80; + } + return 2; + } + else if(in < 0x10000) + { + if(out != NULL) + { + *out++ = (in >> 12) + 0xE0; + *out++ = ((in >> 6) & 0x3F) + 0x80; + *out++ = (in & 0x3F) + 0x80; + } + return 3; + } + else if(in < 0x110000) + { + if(out != NULL) + { + *out++ = (in >> 18) + 0xF0; + *out++ = ((in >> 12) & 0x3F) + 0x80; + *out++ = ((in >> 6) & 0x3F) + 0x80; + *out++ = (in & 0x3F) + 0x80; + } + return 4; + } + + return -1; +} diff --git a/nx/source/util/utf/utf16_to_utf32.c b/nx/source/util/utf/utf16_to_utf32.c new file mode 100644 index 00000000..fd119842 --- /dev/null +++ b/nx/source/util/utf/utf16_to_utf32.c @@ -0,0 +1,37 @@ +#include "switch/types.h" +#include "switch/util/utf.h" + +ssize_t +utf16_to_utf32(uint32_t *out, + const uint16_t *in, + size_t len) +{ + ssize_t rc = 0; + ssize_t units; + uint32_t code; + + do + { + units = decode_utf16(&code, in); + if(units == -1) + return -1; + + if(code > 0) + { + in += units; + + if(out != NULL) + { + if(rc < len) + *out++ = code; + } + + if(SSIZE_MAX - 1 >= rc) + ++rc; + else + return -1; + } + } while(code > 0); + + return rc; +} diff --git a/nx/source/util/utf/utf16_to_utf8.c b/nx/source/util/utf/utf16_to_utf8.c new file mode 100644 index 00000000..187531f3 --- /dev/null +++ b/nx/source/util/utf/utf16_to_utf8.c @@ -0,0 +1,50 @@ +#include "switch/types.h" +#include "switch/util/utf.h" + +ssize_t +utf16_to_utf8(uint8_t *out, + const uint16_t *in, + size_t len) +{ + ssize_t rc = 0; + ssize_t units; + uint32_t code; + uint8_t encoded[4]; + + do + { + units = decode_utf16(&code, in); + if(units == -1) + return -1; + + if(code > 0) + { + in += units; + + units = encode_utf8(encoded, code); + if(units == -1) + return -1; + + if(out != NULL) + { + if(rc + units <= len) + { + *out++ = encoded[0]; + if(units > 1) + *out++ = encoded[1]; + if(units > 2) + *out++ = encoded[2]; + if(units > 3) + *out++ = encoded[3]; + } + } + + if(SSIZE_MAX - units >= rc) + rc += units; + else + return -1; + } + } while(code > 0); + + return rc; +} diff --git a/nx/source/util/utf/utf32_to_utf16.c b/nx/source/util/utf/utf32_to_utf16.c new file mode 100644 index 00000000..d3385153 --- /dev/null +++ b/nx/source/util/utf/utf32_to_utf16.c @@ -0,0 +1,36 @@ +#include "switch/types.h" +#include "switch/util/utf.h" + +ssize_t +utf32_to_utf16(uint16_t *out, + const uint32_t *in, + size_t len) +{ + ssize_t rc = 0; + ssize_t units; + uint16_t encoded[2]; + + while(*in > 0) + { + units = encode_utf16(encoded, *in++); + if(units == -1) + return -1; + + if(out != NULL) + { + if(rc + units <= len) + { + *out++ = encoded[0]; + if(units > 1) + *out++ = encoded[1]; + } + } + + if(SSIZE_MAX - units >= rc) + rc += units; + else + return -1; + } + + return rc; +} diff --git a/nx/source/util/utf/utf32_to_utf8.c b/nx/source/util/utf/utf32_to_utf8.c new file mode 100644 index 00000000..f195765e --- /dev/null +++ b/nx/source/util/utf/utf32_to_utf8.c @@ -0,0 +1,40 @@ +#include "switch/types.h" +#include "switch/util/utf.h" + +ssize_t +utf32_to_utf8(uint8_t *out, + const uint32_t *in, + size_t len) +{ + ssize_t rc = 0; + ssize_t units; + uint8_t encoded[4]; + + while(*in > 0) + { + units = encode_utf8(encoded, *in++); + if(units == -1) + return -1; + + if(out != NULL) + { + if(rc + units <= len) + { + *out++ = encoded[0]; + if(units > 1) + *out++ = encoded[1]; + if(units > 2) + *out++ = encoded[2]; + if(units > 3) + *out++ = encoded[3]; + } + } + + if(SSIZE_MAX - units >= rc) + rc += units; + else + return -1; + } + + return rc; +} diff --git a/nx/source/util/utf/utf8_to_utf16.c b/nx/source/util/utf/utf8_to_utf16.c new file mode 100644 index 00000000..07878860 --- /dev/null +++ b/nx/source/util/utf/utf8_to_utf16.c @@ -0,0 +1,46 @@ +#include "switch/types.h" +#include "switch/util/utf.h" + +ssize_t +utf8_to_utf16(uint16_t *out, + const uint8_t *in, + size_t len) +{ + ssize_t rc = 0; + ssize_t units; + uint32_t code; + uint16_t encoded[2]; + + do + { + units = decode_utf8(&code, in); + if(units == -1) + return -1; + + if(code > 0) + { + in += units; + + units = encode_utf16(encoded, code); + if(units == -1) + return -1; + + if(out != NULL) + { + if(rc + units <= len) + { + *out++ = encoded[0]; + if(units > 1) + *out++ = encoded[1]; + } + } + + if(SSIZE_MAX - units >= rc) + rc += units; + else + return -1; + } + } while(code > 0); + + return rc; +} diff --git a/nx/source/util/utf/utf8_to_utf32.c b/nx/source/util/utf/utf8_to_utf32.c new file mode 100644 index 00000000..ada522e5 --- /dev/null +++ b/nx/source/util/utf/utf8_to_utf32.c @@ -0,0 +1,37 @@ +#include "switch/types.h" +#include "switch/util/utf.h" + +ssize_t +utf8_to_utf32(uint32_t *out, + const uint8_t *in, + size_t len) +{ + ssize_t rc = 0; + ssize_t units; + uint32_t code; + + do + { + units = decode_utf8(&code, in); + if(units == -1) + return -1; + + if(code > 0) + { + in += units; + + if(out != NULL) + { + if(rc < len) + *out++ = code; + } + + if(SSIZE_MAX - 1 >= rc) + ++rc; + else + return -1; + } + } while(code > 0); + + return rc; +}