Add all the files

This commit is contained in:
Exzap 2022-08-22 22:21:23 +02:00
parent e3db07a16a
commit d60742f52b
1445 changed files with 430238 additions and 0 deletions

801
src/Cafe/Filesystem/fsc.cpp Normal file
View file

@ -0,0 +1,801 @@
#include "Cafe/Filesystem/fsc.h"
struct FSCMountPathNode
{
std::string path;
std::vector<FSCMountPathNode*> subnodes;
FSCMountPathNode* parent;
// device target and path (if list_subnodes is nullptr)
fscDeviceC* device{ nullptr };
void* ctx{ nullptr };
std::wstring targetPath;
// priority
sint32 priority{};
FSCMountPathNode(FSCMountPathNode* parent) : parent(parent)
{
}
~FSCMountPathNode()
{
for (auto& itr : subnodes)
delete itr;
subnodes.clear();
}
};
// compare two file or directory names using FS rules
bool FSA_CompareNodeName(std::string_view a, std::string_view b)
{
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); i++)
{
uint8 ac = (uint8)a[i];
uint8 bc = (uint8)b[i];
// lower case compare
if (ac >= (uint8)'A' && ac <= (uint8)'Z')
ac -= ((uint8)'A' - (uint8)'a');
if (bc >= (uint8)'A' && bc <= (uint8)'Z')
bc -= ((uint8)'A' - (uint8)'a');
if (ac != bc)
return false;
}
return true;
}
FSCMountPathNode* s_fscRootNodePerPrio[FSC_PRIORITY_COUNT]{};
std::recursive_mutex s_fscMutex;
#define fscEnter() s_fscMutex.lock();
#define fscLeave() s_fscMutex.unlock();
FSCMountPathNode* fsc_lookupPathVirtualNode(const char* path, sint32 priority = FSC_PRIORITY_BASE);
void fsc_reset()
{
// delete existing nodes
for (auto& itr : s_fscRootNodePerPrio)
{
delete itr;
itr = nullptr;
}
// init root node for each priority
for (sint32 i = 0; i < FSC_PRIORITY_COUNT; i++)
s_fscRootNodePerPrio[i] = new FSCMountPathNode(nullptr);
}
/*
* Creates a node chain for the given mount path. Returns the bottom node.
* If the path already exists for the given priority, NULL is returned (we can't mount two devices to the same path with the same priority)
* But we can map devices to subdirectories. Something like this is possible:
* /vol/content -> Map to WUD (includes all subdirectories except /data, which is handled by the entry below. This exclusion rule applies only if the priority of both mount entries is the same)
* /vol/content/data -> Map to HostFS
* If overlapping paths with different priority are created, then the higher priority one will be checked first
*/
FSCMountPathNode* fsc_createMountPath(CoreinitFSParsedPath* parsedMountPath, sint32 priority)
{
cemu_assert(priority >= 0 && priority < FSC_PRIORITY_COUNT);
fscEnter();
FSCMountPathNode* nodeParent = s_fscRootNodePerPrio[priority];
for(sint32 i=0; i<parsedMountPath->numNodes; i++)
{
// search for subdirectory
FSCMountPathNode* nodeSub = nullptr; // set if we found a subnode with a matching name, else this is used to store the new nodes
for(auto& nodeItr : nodeParent->subnodes)
{
if( coreinitFS_checkNodeName(parsedMountPath, i, nodeItr->path.c_str()) )
{
// subnode found
nodeSub = nodeItr;
break;
}
}
if( nodeSub )
{
// traverse subnode
nodeParent = nodeSub;
continue;
}
// no matching subnode, add new entry
nodeSub = new FSCMountPathNode(nodeParent);
nodeSub->path = coreinitFS_getNodeName(parsedMountPath, i);
nodeSub->priority = priority;
nodeParent->subnodes.emplace_back(nodeSub);
if( i == (parsedMountPath->numNodes-1) )
{
// last node
fscLeave();
return nodeSub;
}
// traverse subnode
nodeParent = nodeSub;
}
// path is empty or already mounted
fscLeave();
if (parsedMountPath->numNodes == 0)
return nodeParent;
return nullptr;
}
// Map a virtual FSC directory to a device and device directory
sint32 fsc_mount(const char* mountPath, const wchar_t* _targetPath, fscDeviceC* fscDevice, void* ctx, sint32 priority)
{
cemu_assert(fscDevice); // device must not be nullptr
// make sure the target path ends with a slash
std::wstring targetPath(_targetPath);
if (!targetPath.empty() && (targetPath.back() != '/' && targetPath.back() != '\\'))
targetPath.push_back('/');
// parse mount path
CoreinitFSParsedPath parsedMountPath;
coreinitFS_parsePath(&parsedMountPath, mountPath);
// register path
fscEnter();
FSCMountPathNode* node = fsc_createMountPath(&parsedMountPath, priority);
if( !node )
{
// path empty, invalid or already used
cemuLog_log(LogType::Force, "fsc_mount failed (virtual path: %s)", mountPath);
fscLeave();
return FSC_STATUS_INVALID_PATH;
}
node->device = fscDevice;
node->ctx = ctx;
node->targetPath = targetPath;
fscLeave();
return FSC_STATUS_OK;
}
bool fsc_unmount(const char* mountPath, sint32 priority)
{
CoreinitFSParsedPath parsedMountPath;
coreinitFS_parsePath(&parsedMountPath, mountPath);
fscEnter();
FSCMountPathNode* mountPathNode = fsc_lookupPathVirtualNode(mountPath, priority);
if (!mountPathNode)
{
fscLeave();
return false;
}
cemu_assert(mountPathNode->priority == priority);
cemu_assert(mountPathNode->device);
// delete node
while (mountPathNode && mountPathNode->parent)
{
FSCMountPathNode* parent = mountPathNode->parent;
cemu_assert(!(!mountPathNode->subnodes.empty() && mountPathNode->device));
if (!mountPathNode->subnodes.empty())
break;
parent->subnodes.erase(std::find(parent->subnodes.begin(), parent->subnodes.end(), mountPathNode));
delete mountPathNode;
mountPathNode = parent;
}
fscLeave();
return true;
}
void fsc_unmountAll()
{
fscEnter();
fsc_reset();
fscLeave();
}
// lookup virtual path and find mounted device and relative device directory
bool fsc_lookupPath(const char* path, std::wstring& devicePathOut, fscDeviceC** fscDeviceOut, void** ctxOut, sint32 priority = FSC_PRIORITY_BASE)
{
// parse path
CoreinitFSParsedPath parsedPath;
coreinitFS_parsePath(&parsedPath, path);
FSCMountPathNode* nodeParent = s_fscRootNodePerPrio[priority];
sint32 i;
fscEnter();
for (i = 0; i < parsedPath.numNodes; i++)
{
// search for subdirectory
FSCMountPathNode* nodeSub = nullptr;
for(auto& nodeItr : nodeParent->subnodes)
{
if (coreinitFS_checkNodeName(&parsedPath, i, nodeItr->path.c_str()))
{
nodeSub = nodeItr;
break;
}
}
if (nodeSub)
{
nodeParent = nodeSub;
continue;
}
// no matching subnode
break;
}
// find deepest device mount point
while (nodeParent)
{
if (nodeParent->device)
{
devicePathOut = nodeParent->targetPath;
for (sint32 f = i; f < parsedPath.numNodes; f++)
{
const char* nodeName = coreinitFS_getNodeName(&parsedPath, f);
devicePathOut.append(boost::nowide::widen(nodeName));
if (f < (parsedPath.numNodes - 1))
devicePathOut.push_back('/');
}
*fscDeviceOut = nodeParent->device;
*ctxOut = nodeParent->ctx;
fscLeave();
return true;
}
nodeParent = nodeParent->parent;
i--;
}
fscLeave();
return false;
}
// lookup path and find virtual device node
FSCMountPathNode* fsc_lookupPathVirtualNode(const char* path, sint32 priority)
{
// parse path
CoreinitFSParsedPath parsedPath;
coreinitFS_parsePath(&parsedPath, path);
FSCMountPathNode* nodeCurrentDir = s_fscRootNodePerPrio[priority];
sint32 i;
fscEnter();
for (i = 0; i < parsedPath.numNodes; i++)
{
// search for subdirectory
FSCMountPathNode* nodeSub = nullptr;
for (auto& nodeItr : nodeCurrentDir->subnodes)
{
if (coreinitFS_checkNodeName(&parsedPath, i, nodeItr->path.c_str()))
{
nodeSub = nodeItr;
break;
}
}
if (nodeSub)
{
// traverse subdirectory
nodeCurrentDir = nodeSub;
continue;
}
fscLeave();
return nullptr;
}
fscLeave();
return nodeCurrentDir;
}
// this wraps multiple iterated directories from different devices into one unified virtual representation
class FSCVirtualFileDirectoryIterator : public FSCVirtualFile
{
public:
sint32 fscGetType() override
{
return FSC_TYPE_DIRECTORY;
}
FSCVirtualFileDirectoryIterator(std::string_view path, std::span<FSCVirtualFile*> mappedFolders)
: m_path(path), m_folders(mappedFolders.begin(), mappedFolders.end())
{
dirIterator = nullptr;
}
~FSCVirtualFileDirectoryIterator()
{
// dirIterator is deleted in base constructor
for (auto& itr : m_folders)
delete itr;
}
bool fscDirNext(FSCDirEntry* dirEntry) override
{
if (!dirIterator)
{
// lazily populate list only if directory is actually iterated
PopulateIterationList();
cemu_assert_debug(dirIterator);
}
if (dirIterator->index >= dirIterator->dirEntries.size())
return false;
*dirEntry = dirIterator->dirEntries[dirIterator->index];
dirIterator->index++;
return true;
}
void addUniqueDirEntry(const FSCDirEntry& dirEntry)
{
// skip if already in list
for (auto& itr : dirIterator->dirEntries)
{
if (FSA_CompareNodeName(dirEntry.path, itr.path))
return;
}
dirIterator->dirEntries.emplace_back(dirEntry);
}
private:
void PopulateIterationList()
{
cemu_assert_debug(!dirIterator);
dirIterator = new FSCVirtualFile::FSCDirIteratorState();
FSCDirEntry dirEntry;
fscEnter();
for (auto& itr : m_folders)
{
while (itr->fscDirNext(&dirEntry))
addUniqueDirEntry(dirEntry);
}
for (sint32 prio = FSC_PRIORITY_COUNT - 1; prio >= 0; prio--)
{
FSCMountPathNode* nodeVirtualPath = fsc_lookupPathVirtualNode(m_path.c_str(), prio);
if (nodeVirtualPath)
{
for (auto& itr : nodeVirtualPath->subnodes)
{
dirEntry = {};
dirEntry.isDirectory = true;
strncpy(dirEntry.path, itr->path.c_str(), sizeof(dirEntry.path) - 1);
dirEntry.path[sizeof(dirEntry.path) - 1] = '\0';
dirEntry.fileSize = 0;
addUniqueDirEntry(dirEntry);
}
}
}
fscLeave();
}
private:
std::string m_path;
std::vector<FSCVirtualFile*> m_folders; // list of all folders mapped to the same directory (at different priorities)
};
// Open file or directory from virtual file system
FSCVirtualFile* fsc_open(const char* path, FSC_ACCESS_FLAG accessFlags, sint32* fscStatus, sint32 maxPriority)
{
cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) || HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)); // must open either file or directory
FSCVirtualFile* dirList[FSC_PRIORITY_COUNT];
uint8 dirListCount = 0;
std::wstring devicePath;
fscDeviceC* fscDevice = NULL;
*fscStatus = FSC_STATUS_UNDEFINED;
void* ctx;
fscEnter();
for (sint32 prio = maxPriority; prio >= 0; prio--)
{
if (fsc_lookupPath(path, devicePath, &fscDevice, &ctx, prio))
{
FSCVirtualFile* fscVirtualFile = fscDevice->fscDeviceOpenByPath(devicePath, accessFlags, ctx, fscStatus);
if (fscVirtualFile)
{
if (fscVirtualFile->fscGetType() == FSC_TYPE_DIRECTORY)
{
cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR));
// collect all folders
dirList[dirListCount] = fscVirtualFile;
dirListCount++;
}
else
{
// return first found file
cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE));
fscLeave();
return fscVirtualFile;
}
}
}
}
// for directories we create a virtual representation of the enumerated files of all priorities as well as the FSC folder structure itself
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
{
// create a virtual directory VirtualFile that represents all the mounted folders as well as the virtual FSC folder structure
bool folderExists = dirListCount > 0;
for (sint32 prio = FSC_PRIORITY_COUNT - 1; prio >= 0; prio--)
{
if (folderExists)
break;
folderExists |= (fsc_lookupPathVirtualNode(path, prio) != 0);
}
if (folderExists)
{
FSCVirtualFileDirectoryIterator* dirIteratorFile = new FSCVirtualFileDirectoryIterator(path, { dirList, dirListCount});
*fscStatus = FSC_STATUS_OK;
fscLeave();
return dirIteratorFile;
}
}
else
{
cemu_assert_debug(dirListCount == 0);
}
fscLeave();
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
return nullptr;
}
/*
* Open file using virtual path
*/
FSCVirtualFile* fsc_openDirIterator(const char* path, sint32* fscStatus)
{
return fsc_open(path, FSC_ACCESS_FLAG::OPEN_DIR, fscStatus);
}
/*
* Iterate next node in directory
* Returns false if there is no node left
*/
bool fsc_nextDir(FSCVirtualFile* fscFile, FSCDirEntry* dirEntry)
{
fscEnter();
if (fscFile->fscGetType() != FSC_TYPE_DIRECTORY)
{
cemu_assert_suspicious();
fscLeave();
return false;
}
bool r = fscFile->fscDirNext(dirEntry);
fscLeave();
return r;
}
/*
* Create directory
*/
bool fsc_createDir(char* path, sint32* fscStatus)
{
fscDeviceC* fscDevice = NULL;
*fscStatus = FSC_STATUS_UNDEFINED;
void* ctx;
std::wstring devicePath;
fscEnter();
if( fsc_lookupPath(path, devicePath, &fscDevice, &ctx) )
{
sint32 status = fscDevice->fscDeviceCreateDir(devicePath, ctx, fscStatus);
fscLeave();
return status;
}
fscLeave();
return false;
}
/*
* Rename file or directory
*/
bool fsc_rename(char* srcPath, char* dstPath, sint32* fscStatus)
{
std::wstring srcDevicePath;
std::wstring dstDevicePath;
void* srcCtx;
void* dstCtx;
fscDeviceC* fscSrcDevice = NULL;
fscDeviceC* fscDstDevice = NULL;
*fscStatus = FSC_STATUS_UNDEFINED;
if( fsc_lookupPath(srcPath, srcDevicePath, &fscSrcDevice, &srcCtx) && fsc_lookupPath(dstPath, dstDevicePath, &fscDstDevice, &dstCtx) )
{
if( fscSrcDevice == fscDstDevice )
return fscSrcDevice->fscDeviceRename(srcDevicePath, dstDevicePath, srcCtx, fscStatus);
}
return false;
}
/*
* Delete file or subdirectory
*/
bool fsc_remove(char* path, sint32* fscStatus)
{
std::wstring devicePath;
fscDeviceC* fscDevice = NULL;
*fscStatus = FSC_STATUS_UNDEFINED;
void* ctx;
if( fsc_lookupPath(path, devicePath, &fscDevice, &ctx) )
{
return fscDevice->fscDeviceRemoveFileOrDir(devicePath, ctx, fscStatus);
}
return false;
}
/*
* Close file handle
*/
void fsc_close(FSCVirtualFile* fscFile)
{
fscEnter();
delete fscFile;
fscLeave();
}
/*
* Return size of file
*/
uint32 fsc_getFileSize(FSCVirtualFile* fscFile)
{
return (uint32)fscFile->fscQueryValueU64(FSC_QUERY_SIZE);
}
/*
* Return file position
*/
uint32 fsc_getFileSeek(FSCVirtualFile* fscFile)
{
return (uint32)fscFile->fscGetSeek();
}
/*
* Set file seek
* For writable files the seek pointer can be set past the end of the file
*/
void fsc_setFileSeek(FSCVirtualFile* fscFile, uint32 newSeek)
{
fscEnter();
uint32 fileSize = fsc_getFileSize(fscFile);
if (fsc_isWritable(fscFile) == false)
newSeek = std::min(newSeek, fileSize);
fscFile->fscSetSeek((uint64)newSeek);
fscLeave();
}
// set file length
void fsc_setFileLength(FSCVirtualFile* fscFile, uint32 newEndOffset)
{
fscEnter();
uint32 fileSize = fsc_getFileSize(fscFile);
if (!fsc_isWritable(fscFile))
{
cemuLog_force("TruncateFile called on read-only file");
}
else
{
fscFile->fscSetFileLength((uint64)newEndOffset);
}
fscLeave();
}
/*
* Returns true if the file object is a directory
*/
bool fsc_isDirectory(FSCVirtualFile* fscFile)
{
return fscFile->fscGetType() == FSC_TYPE_DIRECTORY;
}
/*
* Returns true if the file object is a file
*/
bool fsc_isFile(FSCVirtualFile* fscFile)
{
return fscFile->fscGetType() == FSC_TYPE_FILE;
}
/*
* Returns true if the file is writable
*/
bool fsc_isWritable(FSCVirtualFile* fscFile)
{
return fscFile->fscQueryValueU64(FSC_QUERY_WRITEABLE) != 0;
}
/*
* Read data from file
* Returns number of bytes successfully read
*/
uint32 fsc_readFile(FSCVirtualFile* fscFile, void* buffer, uint32 size)
{
fscEnter();
uint32 fscStatus = fscFile->fscReadData(buffer, size);
fscLeave();
return fscStatus;
}
/*
* Write data to file
* Returns number of bytes successfully written
*/
uint32 fsc_writeFile(FSCVirtualFile* fscFile, void* buffer, uint32 size)
{
fscEnter();
if (fsc_isWritable(fscFile) == false)
{
fscLeave();
return 0;
}
uint32 fscStatus = fscFile->fscWriteData(buffer, size);
fscLeave();
return fscStatus;
}
// helper function to load a file into memory
uint8* fsc_extractFile(const char* path, uint32* fileSize, sint32 maxPriority)
{
fscDeviceC* fscDevice = nullptr;
sint32 fscStatus = FSC_STATUS_UNDEFINED;
fscEnter();
FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus, maxPriority);
if( !fscFile )
{
*fileSize = 0;
fscLeave();
return nullptr;
}
uint32 fscFileSize = fsc_getFileSize(fscFile);
*fileSize = fscFileSize;
uint8* fileMem = (uint8*)malloc(fscFileSize);
if( fsc_readFile(fscFile, fileMem, fscFileSize) != fscFileSize )
{
free(fileMem);
fsc_close(fscFile);
*fileSize = 0;
fscLeave();
return nullptr;
}
fsc_close(fscFile);
fscLeave();
return fileMem;
}
std::optional<std::vector<uint8>> fsc_extractFile(const char* path, sint32 maxPriority)
{
fscDeviceC* fscDevice = nullptr;
sint32 fscStatus = FSC_STATUS_UNDEFINED;
fscEnter();
FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus, maxPriority);
if (!fscFile)
{
fscLeave();
return std::nullopt;
}
std::vector<uint8> fileData;
uint32 fscFileSize = fsc_getFileSize(fscFile);
fileData.resize(fscFileSize);
uint32 readOffset = 0;
while (readOffset < fscFileSize)
{
uint32 stepReadSize = std::min(fscFileSize - readOffset, (uint32)1024 * 1024 * 32);
uint32 numBytesRead = fsc_readFile(fscFile, fileData.data() + readOffset, stepReadSize);
if (numBytesRead != stepReadSize)
{
fsc_close(fscFile);
fscLeave();
return std::nullopt;
}
readOffset += stepReadSize;
}
fsc_close(fscFile);
fscLeave();
return fileData;
}
// helper function to check if a file exists
bool fsc_doesFileExist(const char* path, sint32 maxPriority)
{
fscDeviceC* fscDevice = nullptr;
sint32 fscStatus = FSC_STATUS_UNDEFINED;
fscEnter();
FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus, maxPriority);
if (!fscFile)
{
fscLeave();
return false;
}
fsc_close(fscFile);
fscLeave();
return true;
}
// helper function to check if a folder exists
bool fsc_doesDirectoryExist(const char* path, sint32 maxPriority)
{
fscDeviceC* fscDevice = nullptr;
sint32 fscStatus = FSC_STATUS_UNDEFINED;
fscEnter();
FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_DIR, &fscStatus, maxPriority);
if (!fscFile)
{
fscLeave();
return false;
}
fsc_close(fscFile);
fscLeave();
return true;
}
void coreinitFS_parsePath(CoreinitFSParsedPath* parsedPath, const char* path)
{
// if the path starts with a '/', skip it
if (*path == '/')
path++;
// init parsedPath struct
memset(parsedPath, 0x00, sizeof(CoreinitFSParsedPath));
// init parsed path data
size_t pathLength = std::min((size_t)640, strlen(path));
memcpy(parsedPath->pathData, path, pathLength);
// start parsing
sint32 offset = 0;
sint32 startOffset = 0;
if (offset < pathLength)
{
parsedPath->nodeOffset[parsedPath->numNodes] = offset;
parsedPath->numNodes++;
}
while (offset < pathLength)
{
if (parsedPath->pathData[offset] == '/' || parsedPath->pathData[offset] == '\\')
{
parsedPath->pathData[offset] = '\0';
offset++;
// double slashes are ignored and instead are handled like a single slash
if (parsedPath->pathData[offset] == '/' || parsedPath->pathData[offset] == '\\')
{
// if we're in the beginning and having a \\ it's a network path
if (offset != 1)
{
parsedPath->pathData[offset] = '\0';
offset++;
}
}
// start new node
if (parsedPath->numNodes < FSC_PARSED_PATH_NODES_MAX)
{
if (offset < pathLength)
{
parsedPath->nodeOffset[parsedPath->numNodes] = offset;
parsedPath->numNodes++;
}
}
continue;
}
offset++;
}
// handle special nodes like '.' or '..'
sint32 nodeIndex = 0;
while (nodeIndex < parsedPath->numNodes)
{
if (coreinitFS_checkNodeName(parsedPath, nodeIndex, ".."))
cemu_assert_suspicious(); // how does Cafe OS handle .. ?
else if (coreinitFS_checkNodeName(parsedPath, nodeIndex, "."))
{
// remove this node and shift back all following nodes by 1
parsedPath->numNodes--;
for (sint32 i = nodeIndex; i < parsedPath->numNodes; i++)
{
parsedPath->nodeOffset[i] = parsedPath->nodeOffset[i + 1];
}
// continue without increasing nodeIndex
continue;
}
nodeIndex++;
}
}
bool coreinitFS_checkNodeName(CoreinitFSParsedPath* parsedPath, sint32 index, const char* name)
{
if (index < 0 || index >= parsedPath->numNodes)
return false;
char* nodeName = parsedPath->pathData + parsedPath->nodeOffset[index];
if (boost::iequals(nodeName, name))
return true;
return false;
}
char* coreinitFS_getNodeName(CoreinitFSParsedPath* parsedPath, sint32 index)
{
if (index < 0 || index >= parsedPath->numNodes)
return nullptr;
return parsedPath->pathData + parsedPath->nodeOffset[index];
}
// Initialize Cemu's virtual filesystem
void fsc_init()
{
fsc_reset();
}