mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-11-09 13:12:37 -05:00
3ds: 3ds port sources. Second set of two.
This commit adds new files written exclusively for the 3ds port. Additional comments: 1. Plugins works, but will be enabled in future commits. 2. The port has only been tested on the New 3DS. 3. Not all features of rockbox have been tested so there may be bugs or non-functional features. 4. There is a known issue where a random crash can occur when exiting the app. Change-Id: I122d0bea9aa604e04fca45ba8287cf79e6110769
This commit is contained in:
parent
a4de1195cd
commit
3b7dafb117
51 changed files with 7322 additions and 0 deletions
403
firmware/target/hosted/ctru/lib/sys_dir.c
Normal file
403
firmware/target/hosted/ctru/lib/sys_dir.c
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Mauricio G.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#define DIRFUNCTIONS_DEFINED
|
||||
#include "config.h"
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "debug.h"
|
||||
#include "dir.h"
|
||||
#include "pathfuncs.h"
|
||||
#include "timefuncs.h"
|
||||
#include "system.h"
|
||||
#include "fs_defines.h"
|
||||
#include "sys_file.h"
|
||||
|
||||
#include <3ds/archive.h>
|
||||
#include <3ds/util/utf.h>
|
||||
|
||||
/* This file is based on firmware/common/dir.c */
|
||||
|
||||
/* Define LOGF_ENABLE to enable logf output in this file */
|
||||
// #define LOGF_ENABLE
|
||||
#include "logf.h"
|
||||
|
||||
/* structure used for open directory streams */
|
||||
static struct dirstr_desc
|
||||
{
|
||||
struct filestr_base stream; /* basic stream info (first!) */
|
||||
struct dirent entry; /* current parsed entry information */
|
||||
} open_streams[MAX_OPEN_DIRS] =
|
||||
{
|
||||
[0 ... MAX_OPEN_FILES-1] = { .stream = { .handle = 0 } }
|
||||
};
|
||||
|
||||
extern FS_Archive sdmcArchive;
|
||||
|
||||
/* check and return a struct dirstr_desc* from a DIR* */
|
||||
static struct dirstr_desc * get_dirstr(DIR *dirp)
|
||||
{
|
||||
struct dirstr_desc *dir = (struct dirstr_desc *)dirp;
|
||||
|
||||
if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
|
||||
dir = NULL;
|
||||
else if (dir->stream.handle != 0)
|
||||
return dir;
|
||||
|
||||
int errnum;
|
||||
|
||||
if (!dir)
|
||||
{
|
||||
errnum = EFAULT;
|
||||
}
|
||||
else
|
||||
{
|
||||
logf("dir #%d: dir not open\n", (int)(dir - open_streams));
|
||||
errnum = EBADF;
|
||||
}
|
||||
|
||||
errno = errnum;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define GET_DIRSTR(type, dirp) \
|
||||
({ \
|
||||
file_internal_lock_##type(); \
|
||||
struct dirstr_desc *_dir = get_dirstr(dirp); \
|
||||
if (_dir) \
|
||||
FILESTR_LOCK(type, &_dir->stream); \
|
||||
else { \
|
||||
file_internal_unlock_##type(); \
|
||||
} \
|
||||
_dir; \
|
||||
})
|
||||
|
||||
/* release the lock on the dirstr_desc* */
|
||||
#define RELEASE_DIRSTR(type, dir) \
|
||||
({ \
|
||||
FILESTR_UNLOCK(type, &(dir)->stream); \
|
||||
file_internal_unlock_##type(); \
|
||||
})
|
||||
|
||||
|
||||
/* find a free dir stream descriptor */
|
||||
static struct dirstr_desc * alloc_dirstr(void)
|
||||
{
|
||||
for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++)
|
||||
{
|
||||
struct dirstr_desc *dir = &open_streams[dd];
|
||||
if (dir->stream.handle == 0)
|
||||
return dir;
|
||||
}
|
||||
|
||||
logf("Too many dirs open\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u32 fs_error(void) {
|
||||
u32 err;
|
||||
FSUSER_GetSdmcFatfsError(&err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Initialize the base descriptor */
|
||||
static void filestr_base_init(struct filestr_base *stream)
|
||||
{
|
||||
stream->cache = nil;
|
||||
stream->handle = 0;
|
||||
stream->size = 0;
|
||||
LightLock_Init(&stream->mtx);
|
||||
}
|
||||
|
||||
/** POSIX interface **/
|
||||
|
||||
/* open a directory */
|
||||
DIR * ctru_opendir(const char *dirname)
|
||||
{
|
||||
logf("opendir(dirname=\"%s\")\n", dirname);
|
||||
|
||||
DIR *dirp = NULL;
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
int rc;
|
||||
|
||||
struct dirstr_desc * const dir = alloc_dirstr();
|
||||
if (!dir)
|
||||
FILE_ERROR(EMFILE, _RC);
|
||||
|
||||
filestr_base_init(&dir->stream);
|
||||
Result res = FSUSER_OpenDirectory(&dir->stream.handle,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, dirname));
|
||||
if (R_FAILED(res)) {
|
||||
logf("Open failed: %lld\n", fs_error());
|
||||
FILE_ERROR(EMFILE, -1);
|
||||
}
|
||||
|
||||
dir->stream.size = 0;
|
||||
dir->stream.flags = 0;
|
||||
|
||||
/* we will use file path to implement ctru_samedir function */
|
||||
strcpy(dir->stream.path, dirname);
|
||||
|
||||
dirp = (DIR *)dir;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return dirp;
|
||||
}
|
||||
|
||||
/* close a directory stream */
|
||||
int ctru_closedir(DIR *dirp)
|
||||
{
|
||||
int rc;
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
/* needs to work even if marked "nonexistant" */
|
||||
struct dirstr_desc * const dir = (struct dirstr_desc *)dirp;
|
||||
if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
|
||||
FILE_ERROR(EFAULT, -1);
|
||||
|
||||
logf("closedir(dirname=\"%s\")\n", dir->stream.path);
|
||||
|
||||
if (dir->stream.handle == 0)
|
||||
{
|
||||
logf("dir #%d: dir not open\n", (int)(dir - open_streams));
|
||||
FILE_ERROR(EBADF, -2);
|
||||
}
|
||||
|
||||
Result res = FSDIR_Close(dir->stream.handle);
|
||||
if (R_FAILED(res))
|
||||
FILE_ERROR(ERRNO, -3);
|
||||
|
||||
dir->stream.handle = 0;
|
||||
dir->stream.path[0] = '\0';
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
void dirstr_entry_init(FS_DirectoryEntry *dirEntry, struct dirent *entry)
|
||||
{
|
||||
/* clear */
|
||||
memset(entry, 0, sizeof(struct dirent));
|
||||
|
||||
/* attributes */
|
||||
if (dirEntry->attributes & FS_ATTRIBUTE_DIRECTORY)
|
||||
entry->info.attr |= ATTR_DIRECTORY;
|
||||
if (dirEntry->attributes & FS_ATTRIBUTE_HIDDEN)
|
||||
entry->info.attr |= ATTR_HIDDEN;
|
||||
if (dirEntry->attributes & FS_ATTRIBUTE_ARCHIVE)
|
||||
entry->info.attr |= ATTR_ARCHIVE;
|
||||
if (dirEntry->attributes & FS_ATTRIBUTE_READ_ONLY)
|
||||
entry->info.attr |= ATTR_READ_ONLY;
|
||||
|
||||
/* size */
|
||||
entry->info.size = dirEntry->fileSize;
|
||||
|
||||
/* name */
|
||||
uint8_t d_name[0xA0 + 1];
|
||||
memset(d_name, '\0', 0xA0);
|
||||
utf16_to_utf8(d_name, (uint16_t *) &dirEntry->name, 0xA0);
|
||||
memcpy(entry->d_name, d_name, 0xA0);
|
||||
}
|
||||
|
||||
/* read a directory */
|
||||
struct dirent * ctru_readdir(DIR *dirp)
|
||||
{
|
||||
struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
|
||||
if (!dir)
|
||||
FILE_ERROR_RETURN(ERRNO, NULL);
|
||||
|
||||
int rc;
|
||||
struct dirent *res = NULL;
|
||||
|
||||
logf("readdir(dirname=\"%s\")\n", dir->stream.path);
|
||||
|
||||
u32 dataRead = 0;
|
||||
FS_DirectoryEntry dirEntry;
|
||||
Result result = FSDIR_Read(dir->stream.handle,
|
||||
&dataRead,
|
||||
1,
|
||||
&dirEntry);
|
||||
if (R_FAILED(result))
|
||||
FILE_ERROR(EIO, _RC);
|
||||
|
||||
if (dataRead == 0) {
|
||||
/* directory end. return NULL value, no errno */
|
||||
res = NULL;
|
||||
goto file_error;
|
||||
}
|
||||
|
||||
res = &dir->entry;
|
||||
dirstr_entry_init(&dirEntry, res);
|
||||
|
||||
/* time */
|
||||
char full_path[MAX_PATH+1];
|
||||
|
||||
if (!strcmp(PATH_ROOTSTR, dir->stream.path))
|
||||
snprintf(full_path, MAX_PATH, "%s%s", dir->stream.path, res->d_name);
|
||||
else
|
||||
snprintf(full_path, MAX_PATH, "%s/%s", dir->stream.path, res->d_name);
|
||||
|
||||
u64 mtime;
|
||||
archive_getmtime(full_path, &mtime);
|
||||
|
||||
/* DEBUGF("archive_getmtime(%s): %lld\n", full_path, mtime); */
|
||||
|
||||
uint16_t dosdate, dostime;
|
||||
dostime_localtime(mtime, &dosdate, &dostime);
|
||||
res->info.wrtdate = dosdate;
|
||||
res->info.wrttime = dostime;
|
||||
|
||||
file_error:
|
||||
RELEASE_DIRSTR(READER, dir);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* make a directory */
|
||||
int ctru_mkdir(const char *path)
|
||||
{
|
||||
logf("mkdir(path=\"%s\")\n", path);
|
||||
|
||||
int rc;
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
Result res = FSUSER_CreateDirectory(sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, path),
|
||||
0);
|
||||
if (R_FAILED(res))
|
||||
FILE_ERROR(ERRNO, -1);
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* remove a directory */
|
||||
int ctru_rmdir(const char *name)
|
||||
{
|
||||
logf("rmdir(name=\"%s\")\n", name);
|
||||
|
||||
int rc;
|
||||
|
||||
if (name)
|
||||
{
|
||||
/* path may not end with "." */
|
||||
const char *basename;
|
||||
size_t len = path_basename(name, &basename);
|
||||
if (basename[0] == '.' && len == 1)
|
||||
{
|
||||
logf("Invalid path; last component is \".\"\n");
|
||||
FILE_ERROR_RETURN(EINVAL, -9);
|
||||
}
|
||||
}
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
Result res = FSUSER_DeleteDirectory(sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, name));
|
||||
if (R_FAILED(res))
|
||||
FILE_ERROR(ERRNO, -1);
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/** Extended interface **/
|
||||
|
||||
/* return if two directory streams refer to the same directory */
|
||||
int ctru_samedir(DIR *dirp1, DIR *dirp2)
|
||||
{
|
||||
struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1);
|
||||
if (!dir1)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
int rc = -2;
|
||||
|
||||
struct dirstr_desc * const dir2 = get_dirstr(dirp2);
|
||||
if (dir2) {
|
||||
rc = strcmp(dir1->stream.path, dir2->stream.path) == 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
RELEASE_DIRSTR(WRITER, dir1);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* test directory existence (returns 'false' if a file) */
|
||||
bool ctru_dir_exists(const char *dirname)
|
||||
{
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
int rc;
|
||||
|
||||
Handle handle;
|
||||
Result res = FSUSER_OpenDirectory(&handle,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, dirname));
|
||||
if (R_FAILED(res)) {
|
||||
logf("Directory not found: %ld\n", fs_error());
|
||||
FILE_ERROR(EMFILE, -1);
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
if (rc == 0) {
|
||||
FSDIR_Close(handle);
|
||||
}
|
||||
file_internal_unlock_WRITER();
|
||||
return rc == 0 ? true : false;
|
||||
}
|
||||
|
||||
/* get the portable info from the native entry */
|
||||
struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
|
||||
{
|
||||
int rc;
|
||||
if (!dirp || !entry)
|
||||
FILE_ERROR(EFAULT, _RC);
|
||||
|
||||
if (entry->d_name[0] == '\0')
|
||||
FILE_ERROR(ENOENT, _RC);
|
||||
|
||||
if ((file_size_t)entry->info.size > FILE_SIZE_MAX)
|
||||
FILE_ERROR(EOVERFLOW, _RC);
|
||||
|
||||
return (struct dirinfo)
|
||||
{
|
||||
.attribute = entry->info.attr,
|
||||
.size = entry->info.size,
|
||||
.mtime = dostime_mktime(entry->info.wrtdate, entry->info.wrttime),
|
||||
};
|
||||
|
||||
file_error:
|
||||
return (struct dirinfo){ .attribute = 0 };
|
||||
}
|
||||
|
||||
const char* ctru_root_realpath(void)
|
||||
{
|
||||
/* Native only, for APP and SIM see respective filesystem-.c files */
|
||||
return PATH_ROOTSTR; /* rb_namespace.c */
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue