mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-08 07:51:19 +12:00
Add all the files
This commit is contained in:
parent
e3db07a16a
commit
d60742f52b
1445 changed files with 430238 additions and 0 deletions
1113
src/Cafe/Filesystem/FST/FST.cpp
Normal file
1113
src/Cafe/Filesystem/FST/FST.cpp
Normal file
File diff suppressed because it is too large
Load diff
299
src/Cafe/Filesystem/FST/FST.h
Normal file
299
src/Cafe/Filesystem/FST/FST.h
Normal file
|
@ -0,0 +1,299 @@
|
|||
#pragma once
|
||||
#include "Cemu/ncrypto/ncrypto.h"
|
||||
|
||||
struct FSTFileHandle
|
||||
{
|
||||
friend class FSTVolume;
|
||||
private:
|
||||
uint32 m_fstIndex;
|
||||
};
|
||||
|
||||
struct FSTDirectoryIterator
|
||||
{
|
||||
friend class FSTVolume;
|
||||
private:
|
||||
uint32 startIndex;
|
||||
uint32 endIndex;
|
||||
uint32 currentIndex;
|
||||
};
|
||||
|
||||
class FSTVolume
|
||||
{
|
||||
public:
|
||||
static bool FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey);
|
||||
|
||||
static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey);
|
||||
static FSTVolume* OpenFromDiscImage(const fs::path& path, bool* keyFound = nullptr);
|
||||
static FSTVolume* OpenFromContentFolder(fs::path folderPath);
|
||||
|
||||
~FSTVolume();
|
||||
|
||||
uint32 GetFileCount() const;
|
||||
|
||||
bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false);
|
||||
|
||||
// file and directory functions
|
||||
bool IsDirectory(FSTFileHandle& fileHandle) const;
|
||||
bool IsFile(FSTFileHandle& fileHandle) const;
|
||||
bool HasLinkFlag(FSTFileHandle& fileHandle) const;
|
||||
|
||||
std::string_view GetName(FSTFileHandle& fileHandle) const;
|
||||
std::string GetPath(FSTFileHandle& fileHandle) const;
|
||||
|
||||
// file functions
|
||||
uint32 GetFileSize(FSTFileHandle& fileHandle) const;
|
||||
uint32 ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size, void* dataOut);
|
||||
|
||||
// directory iterator
|
||||
bool OpenDirectoryIterator(std::string_view path, FSTDirectoryIterator& directoryIteratorOut);
|
||||
bool Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fileHandleOut);
|
||||
|
||||
// helper function to read whole file
|
||||
std::vector<uint8> ExtractFile(std::string_view path, bool* success = nullptr)
|
||||
{
|
||||
if (success)
|
||||
*success = false;
|
||||
std::vector<uint8> fileData;
|
||||
FSTFileHandle fileHandle;
|
||||
if (!OpenFile(path, fileHandle, true))
|
||||
return fileData;
|
||||
fileData.resize(GetFileSize(fileHandle));
|
||||
ReadFile(fileHandle, 0, (uint32)fileData.size(), fileData.data());
|
||||
if (success)
|
||||
*success = true;
|
||||
return fileData;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/* FST data (in memory) */
|
||||
enum class ClusterHashMode : uint8
|
||||
{
|
||||
RAW = 0, // raw data + encryption, no hashing?
|
||||
RAW2 = 1, // raw data + encryption, with hash stored in tmd?
|
||||
HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data)
|
||||
};
|
||||
|
||||
struct FSTCluster
|
||||
{
|
||||
uint32 offset;
|
||||
uint32 size;
|
||||
ClusterHashMode hashMode;
|
||||
};
|
||||
|
||||
struct FSTEntry
|
||||
{
|
||||
enum class TYPE : uint8
|
||||
{
|
||||
FILE,
|
||||
DIRECTORY,
|
||||
};
|
||||
|
||||
enum FLAGS : uint8
|
||||
{
|
||||
FLAG_NONE = 0x0,
|
||||
FLAG_LINK = 0x1,
|
||||
FLAG_UKN02 = 0x2, // seen in Super Mario Galaxy. Used for vWii files?
|
||||
};
|
||||
|
||||
uint32 nameOffset;
|
||||
uint32 parentDirIndex; // index of parent directory
|
||||
uint16 nameHash;
|
||||
uint8 typeAndFlags;
|
||||
|
||||
TYPE GetType() const
|
||||
{
|
||||
return (TYPE)(typeAndFlags & 0xF);
|
||||
}
|
||||
|
||||
void SetType(TYPE t)
|
||||
{
|
||||
typeAndFlags &= ~0x0F;
|
||||
typeAndFlags |= ((uint8)t);
|
||||
}
|
||||
|
||||
FLAGS GetFlags() const
|
||||
{
|
||||
return (FLAGS)(typeAndFlags >> 4);
|
||||
}
|
||||
|
||||
void SetFlags(FLAGS flags)
|
||||
{
|
||||
typeAndFlags &= ~0xF0;
|
||||
typeAndFlags |= ((uint8)flags << 4);
|
||||
}
|
||||
|
||||
// note: The root node is not considered a valid parent
|
||||
bool HasNonRootNodeParent() const
|
||||
{
|
||||
return parentDirIndex != 0;
|
||||
}
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint32 endIndex;
|
||||
}dirInfo;
|
||||
struct
|
||||
{
|
||||
uint32 fileOffset;
|
||||
uint32 fileSize;
|
||||
uint16 clusterIndex;
|
||||
}fileInfo;
|
||||
};
|
||||
};
|
||||
|
||||
class FSTDataSource* m_dataSource;
|
||||
bool m_sourceIsOwned{};
|
||||
uint32 m_sectorSize{}; // for cluster offsets
|
||||
uint32 m_offsetFactor{}; // for file offsets
|
||||
std::vector<FSTCluster> m_cluster;
|
||||
std::vector<FSTEntry> m_entries;
|
||||
std::vector<char> m_nameStringTable;
|
||||
NCrypto::AesKey m_partitionTitlekey;
|
||||
|
||||
/* Cache for decrypted hashed blocks */
|
||||
std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks;
|
||||
uint64 m_cacheAccessCounter{};
|
||||
|
||||
struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex);
|
||||
|
||||
/* File reading */
|
||||
uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
||||
uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
|
||||
|
||||
/* FST parsing */
|
||||
struct FSTHeader
|
||||
{
|
||||
/* +0x00 */ uint32be magic;
|
||||
/* +0x04 */ uint32be offsetFactor;
|
||||
/* +0x08 */ uint32be numCluster;
|
||||
/* +0x0C */ uint32be ukn0C;
|
||||
/* +0x10 */ uint32be ukn10;
|
||||
/* +0x14 */ uint32be ukn14;
|
||||
/* +0x18 */ uint32be ukn18;
|
||||
/* +0x1C */ uint32be ukn1C;
|
||||
};
|
||||
|
||||
static_assert(sizeof(FSTHeader) == 0x20);
|
||||
|
||||
struct FSTHeader_ClusterEntry
|
||||
{
|
||||
/* +0x00 */ uint32be offset;
|
||||
/* +0x04 */ uint32be size;
|
||||
/* +0x08 */ uint64be ownerTitleId;
|
||||
/* +0x10 */ uint32be groupId;
|
||||
/* +0x14 */ uint8be hashMode;
|
||||
/* +0x15 */ uint8be padding[0xB]; // ?
|
||||
};
|
||||
static_assert(sizeof(FSTHeader_ClusterEntry) == 0x20);
|
||||
|
||||
struct FSTHeader_FileEntry
|
||||
{
|
||||
enum class TYPE : uint8
|
||||
{
|
||||
FILE = 0,
|
||||
DIRECTORY = 1,
|
||||
};
|
||||
|
||||
/* +0x00 */ uint32be typeAndNameOffset;
|
||||
/* +0x04 */ uint32be offset; // for directories: parent directory index
|
||||
/* +0x08 */ uint32be size; // for directories: end index
|
||||
/* +0x0C */ uint16be flagsOrPermissions; // three entries, each one shifted by 4. (so 0xXYZ). Possible bits per value seem to be 0x1 and 0x4 ? These are probably permissions
|
||||
/* +0x0E */ uint16be clusterIndex;
|
||||
|
||||
TYPE GetType()
|
||||
{
|
||||
uint8 v = GetTypeFlagField();
|
||||
cemu_assert_debug((v & ~0x83) == 0); // unknown type/flag
|
||||
return static_cast<TYPE>(v & 0x01);
|
||||
}
|
||||
|
||||
bool HasFlagLink()
|
||||
{
|
||||
uint8 v = GetTypeFlagField();
|
||||
return (v & 0x80) != 0;
|
||||
}
|
||||
|
||||
bool HasUknFlag02()
|
||||
{
|
||||
uint8 v = GetTypeFlagField();
|
||||
return (v & 0x02) != 0;
|
||||
}
|
||||
|
||||
uint32 GetNameOffset()
|
||||
{
|
||||
return (uint32)typeAndNameOffset & 0xFFFFFF;
|
||||
}
|
||||
|
||||
uint32 GetDirectoryParent()
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
|
||||
uint32 GetDirectoryEndIndex()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8 GetTypeFlagField()
|
||||
{
|
||||
return static_cast<uint8>((typeAndNameOffset >> 24) & 0xFF);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(FSTHeader_FileEntry) == 0x10);
|
||||
|
||||
static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode);
|
||||
static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode);
|
||||
static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries);
|
||||
|
||||
bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName)
|
||||
{
|
||||
const char* entryName = m_nameStringTable.data() + entry.nameOffset;
|
||||
const char* comparedNameCur = comparedName.data();
|
||||
const char* comparedNameEnd = comparedName.data() + comparedName.size();
|
||||
while (comparedNameCur < comparedNameEnd)
|
||||
{
|
||||
uint8 c1 = *entryName;
|
||||
uint8 c2 = *comparedNameCur;
|
||||
if (c1 >= (uint8)'A' && c1 <= (uint8)'Z')
|
||||
c1 = c1 - ((uint8)'A' - (uint8)'a');
|
||||
if (c2 >= (uint8)'A' && c2 <= (uint8)'Z')
|
||||
c2 = c2 - ((uint8)'A' - (uint8)'a');
|
||||
if (c1 != c2)
|
||||
return false;
|
||||
entryName++;
|
||||
comparedNameCur++;
|
||||
}
|
||||
return *entryName == '\0'; // all the characters match, check for same length
|
||||
}
|
||||
|
||||
// we utilize hashes to accelerate string comparisons when doing path lookups
|
||||
static uint16 _QuickNameHash(const char* fileName, size_t len)
|
||||
{
|
||||
uint16 v = 0;
|
||||
const char* fileNameEnd = fileName + len;
|
||||
while (fileName < fileNameEnd)
|
||||
{
|
||||
uint8 c = (uint8)*fileName;
|
||||
if (c >= (uint8)'A' && c <= (uint8)'Z')
|
||||
c = c - ((uint8)'A' - (uint8)'a');
|
||||
v += (uint16)c;
|
||||
v = (v >> 3) | (v << 13);
|
||||
fileName++;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class FSTVerifier
|
||||
{
|
||||
public:
|
||||
static bool VerifyContentFile(class FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash);
|
||||
static bool VerifyHashedContentFile(class FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash);
|
||||
|
||||
};
|
130
src/Cafe/Filesystem/FST/KeyCache.cpp
Normal file
130
src/Cafe/Filesystem/FST/KeyCache.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include <wx/msgdlg.h>
|
||||
#include <mutex>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "util/crypto/aes128.h"
|
||||
#include "Common/filestream.h"
|
||||
#include "util/helpers/StringHelpers.h"
|
||||
|
||||
std::mutex mtxKeyCache;
|
||||
|
||||
struct KeyCacheEntry
|
||||
{
|
||||
uint8 aes128key[16];
|
||||
};
|
||||
|
||||
std::vector<KeyCacheEntry> g_keyCache;
|
||||
|
||||
bool strishex(std::string_view str)
|
||||
{
|
||||
for(size_t i=0; i<str.size(); i++)
|
||||
{
|
||||
char c = str[i];
|
||||
if( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') )
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns AES-128 key from the key cache
|
||||
* nullptr is returned if index >= max_keys
|
||||
*/
|
||||
uint8* KeyCache_GetAES128(sint32 index)
|
||||
{
|
||||
if( index < 0 || index >= (sint32)g_keyCache.size())
|
||||
return nullptr;
|
||||
KeyCacheEntry* keyCacheEntry = &g_keyCache[index];
|
||||
return keyCacheEntry->aes128key;
|
||||
}
|
||||
|
||||
void KeyCache_AddKey128(uint8* key)
|
||||
{
|
||||
KeyCacheEntry newEntry = {0};
|
||||
memcpy(newEntry.aes128key, key, 16);
|
||||
g_keyCache.emplace_back(newEntry);
|
||||
}
|
||||
|
||||
bool sKeyCachePrepared = false;
|
||||
|
||||
void KeyCache_Prepare()
|
||||
{
|
||||
mtxKeyCache.lock();
|
||||
if (sKeyCachePrepared)
|
||||
{
|
||||
mtxKeyCache.unlock();
|
||||
return;
|
||||
}
|
||||
sKeyCachePrepared = true;
|
||||
g_keyCache.clear();
|
||||
// load keys
|
||||
auto keysPath = ActiveSettings::GetPath("keys.txt");
|
||||
FileStream* fs_keys = FileStream::openFile2(keysPath);
|
||||
if( !fs_keys )
|
||||
{
|
||||
fs_keys = FileStream::createFile2(keysPath);
|
||||
if(fs_keys)
|
||||
{
|
||||
fs_keys->writeString("# this file contains keys needed for decryption of disc file system data (WUD/WUX)\r\n");
|
||||
fs_keys->writeString("# 1 key per line, any text after a '#' character is considered a comment\r\n");
|
||||
fs_keys->writeString("# the emulator will automatically pick the right key\r\n");
|
||||
fs_keys->writeString("541b9889519b27d363cd21604b97c67a # example key (can be deleted)\r\n");
|
||||
delete fs_keys;
|
||||
}
|
||||
else
|
||||
{
|
||||
wxMessageBox("Unable to create file keys.txt\nThis can happen if Cemu does not have write permission to it's own directory, the disk is full or if anti-virus software is blocking Cemu.", "Error", wxOK | wxCENTRE | wxICON_ERROR);
|
||||
}
|
||||
mtxKeyCache.unlock();
|
||||
return;
|
||||
}
|
||||
sint32 lineNumber = 0;
|
||||
std::string line;
|
||||
while( fs_keys->readLine(line) )
|
||||
{
|
||||
lineNumber++;
|
||||
// truncate anything after '#' or ';'
|
||||
for(size_t i=0; i<line.size(); i++)
|
||||
{
|
||||
if(line[i] == '#' || line[i] == ';' )
|
||||
{
|
||||
line.resize(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// remove whitespaces and other common formatting characters
|
||||
auto itr = line.begin();
|
||||
while (itr != line.end())
|
||||
{
|
||||
char c = *itr;
|
||||
if (c == ' ' || c == '\t' || c == '-' || c == '_')
|
||||
itr = line.erase(itr);
|
||||
else
|
||||
itr++;
|
||||
}
|
||||
if (line.empty())
|
||||
continue;
|
||||
if( strishex(line) == false )
|
||||
{
|
||||
// show error message
|
||||
char errorMsg[512];
|
||||
sprintf(errorMsg, "Error in keys.txt in line %d\n", lineNumber);
|
||||
wxMessageBox(errorMsg, "Error", wxOK | wxCENTRE | wxICON_ERROR);
|
||||
continue;
|
||||
}
|
||||
if(line.size() == 32 )
|
||||
{
|
||||
// 128-bit key
|
||||
uint8 keyData128[16];
|
||||
StringHelpers::ParseHexString(line, keyData128, 16);
|
||||
KeyCache_AddKey128(keyData128);
|
||||
}
|
||||
else
|
||||
{
|
||||
// invalid key length
|
||||
}
|
||||
}
|
||||
delete fs_keys;
|
||||
mtxKeyCache.unlock();
|
||||
}
|
5
src/Cafe/Filesystem/FST/KeyCache.h
Normal file
5
src/Cafe/Filesystem/FST/KeyCache.h
Normal file
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
void KeyCache_Prepare();
|
||||
|
||||
uint8* KeyCache_GetAES128(sint32 index);
|
490
src/Cafe/Filesystem/FST/fstUtil.h
Normal file
490
src/Cafe/Filesystem/FST/fstUtil.h
Normal file
|
@ -0,0 +1,490 @@
|
|||
#pragma once
|
||||
#include <wchar.h>
|
||||
|
||||
class parsedPathW
|
||||
{
|
||||
static const int MAX_NODES = 32;
|
||||
|
||||
public:
|
||||
parsedPathW(std::wstring_view path)
|
||||
{
|
||||
// init vars
|
||||
this->numNodes = 0;
|
||||
// init parsed path data
|
||||
if (path.front() == '/')
|
||||
path.remove_prefix(1);
|
||||
pathData.assign(path.begin(), path.end());
|
||||
pathData.push_back('\0');
|
||||
// start parsing
|
||||
sint32 offset = 0;
|
||||
sint32 startOffset = 0;
|
||||
if (offset < pathData.size()-1)
|
||||
{
|
||||
this->nodeOffset[this->numNodes] = offset;
|
||||
this->numNodes++;
|
||||
}
|
||||
while (offset < pathData.size() - 1)
|
||||
{
|
||||
if (this->pathData[offset] == '/' || this->pathData[offset] == '\\')
|
||||
{
|
||||
this->pathData[offset] = '\0';
|
||||
offset++;
|
||||
// double slashes are ignored and instead are handled like a single slash
|
||||
if (this->pathData[offset] == '/' || this->pathData[offset] == '\\')
|
||||
{
|
||||
// if we're in the beginning and having a \\ it's a network path
|
||||
if (offset != 1)
|
||||
{
|
||||
this->pathData[offset] = '\0';
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
// start new node
|
||||
if (this->numNodes < MAX_NODES)
|
||||
{
|
||||
if (offset < pathData.size() - 1)
|
||||
{
|
||||
this->nodeOffset[this->numNodes] = offset;
|
||||
this->numNodes++;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
// handle special nodes like '.' or '..'
|
||||
sint32 nodeIndex = 0;
|
||||
while (nodeIndex < this->numNodes)
|
||||
{
|
||||
if (compareNodeName(nodeIndex, L".."))
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
else if (compareNodeName(nodeIndex, L"."))
|
||||
{
|
||||
removeNode(nodeIndex);
|
||||
continue;
|
||||
}
|
||||
nodeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the names match (case sensitive)
|
||||
bool compareNodeName(sint32 index, std::wstring_view name)
|
||||
{
|
||||
if (index < 0 || index >= this->numNodes)
|
||||
return false;
|
||||
const wchar_t* nodeName = this->pathData.data() + this->nodeOffset[index];
|
||||
if (boost::iequals(nodeName, name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool compareNodeName(std::wstring_view name1, std::wstring_view name2)
|
||||
{
|
||||
if (boost::iequals(name1, name2))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool compareNodeNameCaseInsensitive(std::wstring_view name1, std::wstring_view name2)
|
||||
{
|
||||
if (boost::iequals(name1, name2))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t* getNodeName(sint32 index)
|
||||
{
|
||||
if (index < 0 || index >= this->numNodes)
|
||||
return nullptr;
|
||||
return this->pathData.data() + this->nodeOffset[index];
|
||||
}
|
||||
|
||||
void removeNode(sint32 index)
|
||||
{
|
||||
if (index < 0 || index >= numNodes)
|
||||
return;
|
||||
numNodes--;
|
||||
for (sint32 i = 0; i < numNodes; i++)
|
||||
{
|
||||
nodeOffset[i] = nodeOffset[i + 1];
|
||||
}
|
||||
// remove empty space
|
||||
if (numNodes > 0)
|
||||
updateOffsets(nodeOffset[0]);
|
||||
}
|
||||
|
||||
void prependNode(wchar_t* newNode)
|
||||
{
|
||||
if (numNodes >= MAX_NODES)
|
||||
return;
|
||||
sint32 len = (sint32)wcslen(newNode);
|
||||
updateOffsets(-(len + 1));
|
||||
numNodes++;
|
||||
for (sint32 i = numNodes - 1; i >= 1; i--)
|
||||
{
|
||||
nodeOffset[i] = nodeOffset[i - 1];
|
||||
}
|
||||
nodeOffset[0] = 0;
|
||||
memcpy(pathData.data() + 0, newNode, (len + 1) * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
void buildPathString(std::wstring& pathStr, bool appendSlash = false)
|
||||
{
|
||||
pathStr.resize(0);
|
||||
for (sint32 i = 0; i < numNodes; i++)
|
||||
{
|
||||
if (numNodes > 1)
|
||||
pathStr.append(L"/");
|
||||
pathStr.append(pathData.data() + nodeOffset[i]);
|
||||
}
|
||||
if(appendSlash)
|
||||
pathStr.append(L"/");
|
||||
}
|
||||
private:
|
||||
void updateOffsets(sint32 newBaseOffset)
|
||||
{
|
||||
if (numNodes == 0 || newBaseOffset == 0)
|
||||
return;
|
||||
cemu_assert_debug(newBaseOffset <= nodeOffset[0]);
|
||||
if (newBaseOffset > 0)
|
||||
{
|
||||
// decrease size
|
||||
memmove(pathData.data(), pathData.data() + newBaseOffset, (pathData.size() - newBaseOffset) * sizeof(wchar_t));
|
||||
pathData.resize(pathData.size() - newBaseOffset);
|
||||
// update node offsets
|
||||
for (sint32 i = 0; i < numNodes; i++)
|
||||
{
|
||||
nodeOffset[i] -= newBaseOffset;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// increase size
|
||||
newBaseOffset = -newBaseOffset;
|
||||
pathData.resize(pathData.size() + newBaseOffset);
|
||||
memmove(pathData.data() + newBaseOffset, pathData.data(), (pathData.size() - newBaseOffset) * sizeof(wchar_t));
|
||||
// update node offsets
|
||||
for (sint32 i = 0; i < numNodes; i++)
|
||||
{
|
||||
nodeOffset[i] += newBaseOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//std::wstring pathData;
|
||||
std::vector<wchar_t> pathData;
|
||||
sint32 nodeOffset[MAX_NODES + 1];
|
||||
|
||||
public:
|
||||
sint32 numNodes;
|
||||
};
|
||||
|
||||
|
||||
template<typename F, bool isCaseSensitive>
|
||||
class FileTree
|
||||
{
|
||||
public:
|
||||
|
||||
private:
|
||||
|
||||
enum NODETYPE : uint8
|
||||
{
|
||||
NODETYPE_DIRECTORY,
|
||||
NODETYPE_FILE,
|
||||
};
|
||||
|
||||
typedef struct _node_t
|
||||
{
|
||||
std::wstring name;
|
||||
std::vector<_node_t*> subnodes;
|
||||
F* custom;
|
||||
NODETYPE type;
|
||||
}node_t;
|
||||
|
||||
node_t* getByNodePath(parsedPathW& p, sint32 numNodes, bool createAsDirectories)
|
||||
{
|
||||
node_t* currentNode = &rootNode;
|
||||
for (sint32 i = 0; i < numNodes; i++)
|
||||
{
|
||||
// find subnode by path
|
||||
node_t* foundSubnode = getSubnode(currentNode, p.getNodeName(i));
|
||||
if (foundSubnode == nullptr)
|
||||
{
|
||||
// no subnode found -> create new directory node (if requested)
|
||||
if (createAsDirectories == false)
|
||||
return nullptr; // path not found
|
||||
currentNode = newNode(currentNode, NODETYPE_DIRECTORY, p.getNodeName(i));
|
||||
}
|
||||
else
|
||||
{
|
||||
currentNode = foundSubnode;
|
||||
}
|
||||
}
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
node_t* getSubnode(node_t* parentNode, std::wstring_view name)
|
||||
{
|
||||
for (auto& sn : parentNode->subnodes)
|
||||
{
|
||||
if constexpr (isCaseSensitive)
|
||||
{
|
||||
if (parsedPathW::compareNodeName(sn->name.c_str(), name))
|
||||
return sn;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parsedPathW::compareNodeNameCaseInsensitive(sn->name.c_str(), name))
|
||||
return sn;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
node_t* newNode(node_t* parentNode, NODETYPE type, std::wstring_view name)
|
||||
{
|
||||
node_t* newNode = new node_t;
|
||||
newNode->name = std::wstring(name);
|
||||
newNode->type = type;
|
||||
newNode->custom = nullptr;
|
||||
parentNode->subnodes.push_back(newNode);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
public:
|
||||
FileTree()
|
||||
{
|
||||
rootNode.type = NODETYPE_DIRECTORY;
|
||||
}
|
||||
|
||||
bool addFile(const wchar_t* path, F* custom)
|
||||
{
|
||||
parsedPathW p(path);
|
||||
if (p.numNodes == 0)
|
||||
return false;
|
||||
node_t* directoryNode = getByNodePath(p, p.numNodes - 1, true);
|
||||
// check if a node with same name already exists
|
||||
if (getSubnode(directoryNode, p.getNodeName(p.numNodes - 1)) != nullptr)
|
||||
return false; // node already exists
|
||||
// add file node
|
||||
node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.getNodeName(p.numNodes - 1));
|
||||
fileNode->custom = custom;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getFile(std::wstring_view path, F* &custom)
|
||||
{
|
||||
parsedPathW p(path);
|
||||
if (p.numNodes == 0)
|
||||
return false;
|
||||
node_t* node = getByNodePath(p, p.numNodes, false);
|
||||
if (node == nullptr)
|
||||
return false;
|
||||
if (node->type != NODETYPE_FILE)
|
||||
return false;
|
||||
custom = node->custom;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool removeFile(std::wstring_view path)
|
||||
{
|
||||
parsedPathW p(path);
|
||||
if (p.numNodes == 0)
|
||||
return false;
|
||||
node_t* directoryNode = getByNodePath(p, p.numNodes - 1, false);
|
||||
if (directoryNode == nullptr)
|
||||
return false;
|
||||
// find node
|
||||
node_t* fileNode = getSubnode(directoryNode, p.getNodeName(p.numNodes - 1));
|
||||
if (fileNode == nullptr)
|
||||
return false;
|
||||
if (fileNode->type != NODETYPE_FILE)
|
||||
return false;
|
||||
if (fileNode->subnodes.empty() == false)
|
||||
{
|
||||
// files shouldn't have subnodes
|
||||
assert(false);
|
||||
}
|
||||
// remove node from parent
|
||||
directoryNode->subnodes.erase(std::remove(directoryNode->subnodes.begin(), directoryNode->subnodes.end(), fileNode), directoryNode->subnodes.end());
|
||||
// delete node
|
||||
delete fileNode;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
bool listDirectory(const wchar_t* path, TFunc fn)
|
||||
{
|
||||
parsedPathW p(path);
|
||||
node_t* node = getByNodePath(p, p.numNodes, false);
|
||||
if (node == nullptr)
|
||||
return false;
|
||||
if (node->type != NODETYPE_DIRECTORY)
|
||||
return false;
|
||||
for (auto& it : node->subnodes)
|
||||
{
|
||||
if (it->type == NODETYPE_DIRECTORY)
|
||||
{
|
||||
fn(it->name, true, it->custom);
|
||||
}
|
||||
else if (it->type == NODETYPE_FILE)
|
||||
{
|
||||
fn(it->name, false, it->custom);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
node_t rootNode;
|
||||
};
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
// path parser and utility class for Wii U paths
|
||||
// optimized to be allocation-free for common path lengths
|
||||
class FSCPath
|
||||
{
|
||||
struct PathNode
|
||||
{
|
||||
PathNode(uint16 offset, uint16 len) : offset(offset), len(len) {};
|
||||
|
||||
uint16 offset;
|
||||
uint16 len;
|
||||
};
|
||||
|
||||
boost::container::small_vector<PathNode, 8> m_nodes;
|
||||
boost::container::small_vector<char, 64> m_names;
|
||||
bool m_isAbsolute{};
|
||||
|
||||
inline bool isSlash(char c)
|
||||
{
|
||||
return c == '\\' || c == '/';
|
||||
}
|
||||
|
||||
void appendNode(const char* name, uint16 nameLen)
|
||||
{
|
||||
if (m_names.size() > 0xFFFF)
|
||||
return;
|
||||
m_nodes.emplace_back((uint16)m_names.size(), nameLen);
|
||||
m_names.insert(m_names.end(), name, name + nameLen);
|
||||
}
|
||||
|
||||
public:
|
||||
FSCPath(std::string_view path)
|
||||
{
|
||||
if (path.empty())
|
||||
return;
|
||||
if (isSlash(path.front()))
|
||||
{
|
||||
m_isAbsolute = true;
|
||||
path.remove_prefix(1);
|
||||
// skip any additional leading slashes
|
||||
while(!path.empty() && isSlash(path.front()))
|
||||
path.remove_prefix(1);
|
||||
}
|
||||
// parse nodes
|
||||
size_t n = 0;
|
||||
size_t nodeNameStartIndex = 0;
|
||||
while (n < path.size())
|
||||
{
|
||||
if (isSlash(path[n]))
|
||||
{
|
||||
size_t nodeNameLen = n - nodeNameStartIndex;
|
||||
if (nodeNameLen > 0xFFFF)
|
||||
nodeNameLen = 0xFFFF; // truncate suspiciously long node names
|
||||
cemu_assert_debug(nodeNameLen > 0);
|
||||
appendNode(path.data() + nodeNameStartIndex, (uint16)nodeNameLen);
|
||||
// skip any repeating slashes
|
||||
while (n < path.size() && isSlash(path[n]))
|
||||
n++;
|
||||
nodeNameStartIndex = n;
|
||||
continue;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
if (nodeNameStartIndex < n)
|
||||
{
|
||||
size_t nodeNameLen = n - nodeNameStartIndex;
|
||||
if (nodeNameLen > 0xFFFF)
|
||||
nodeNameLen = 0xFFFF; // truncate suspiciously long node names
|
||||
appendNode(path.data() + nodeNameStartIndex, (uint16)nodeNameLen);
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetNodeCount() const
|
||||
{
|
||||
return m_nodes.size();
|
||||
}
|
||||
|
||||
std::string_view GetNodeName(size_t index) const
|
||||
{
|
||||
if (index < 0 || index >= m_nodes.size())
|
||||
return std::basic_string_view<char>();
|
||||
return std::basic_string_view<char>(m_names.data() + m_nodes[index].offset, m_nodes[index].len);
|
||||
}
|
||||
|
||||
bool MatchNode(sint32 index, std::string_view name) const
|
||||
{
|
||||
if (index < 0 || index >= (sint32)m_nodes.size())
|
||||
return false;
|
||||
auto nodeName = GetNodeName(index);
|
||||
if (nodeName.size() != name.size())
|
||||
return false;
|
||||
for (size_t i = 0; i < nodeName.size(); i++)
|
||||
{
|
||||
char c1 = nodeName[i];
|
||||
char c2 = name[i];
|
||||
if (c1 >= 'A' && c1 <= 'Z')
|
||||
c1 += ('a' - 'A');
|
||||
if (c2 >= 'A' && c2 <= 'Z')
|
||||
c2 += ('a' - 'A');
|
||||
if (c1 != c2)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static void FSTPathUnitTest()
|
||||
{
|
||||
// test 1
|
||||
FSCPath p1("/vol/content");
|
||||
cemu_assert_debug(p1.GetNodeCount() == 2);
|
||||
cemu_assert_debug(p1.MatchNode(0, "tst") == false);
|
||||
cemu_assert_debug(p1.MatchNode(0, "vol"));
|
||||
cemu_assert_debug(p1.MatchNode(1, "CONTENT"));
|
||||
// test 2
|
||||
FSCPath p2("/vol/content/");
|
||||
cemu_assert_debug(p2.GetNodeCount() == 2);
|
||||
cemu_assert_debug(p2.MatchNode(0, "vol"));
|
||||
cemu_assert_debug(p2.MatchNode(1, "content"));
|
||||
// test 3
|
||||
FSCPath p3("/vol//content/\\/");
|
||||
cemu_assert_debug(p3.GetNodeCount() == 2);
|
||||
cemu_assert_debug(p3.MatchNode(0, "vol"));
|
||||
cemu_assert_debug(p3.MatchNode(1, "content"));
|
||||
// test 4
|
||||
FSCPath p4("vol/content/");
|
||||
cemu_assert_debug(p4.GetNodeCount() == 2);
|
||||
// test 5
|
||||
FSCPath p5("/vol/content/test.bin");
|
||||
cemu_assert_debug(p5.GetNodeCount() == 3);
|
||||
cemu_assert_debug(p5.MatchNode(0, "vol"));
|
||||
cemu_assert_debug(p5.MatchNode(1, "content"));
|
||||
cemu_assert_debug(p5.MatchNode(2, "TEST.BIN"));
|
||||
// test 6 - empty paths
|
||||
FSCPath p6("");
|
||||
cemu_assert_debug(p6.GetNodeCount() == 0);
|
||||
p6 = FSCPath("/////////////");
|
||||
cemu_assert_debug(p6.GetNodeCount() == 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
128
src/Cafe/Filesystem/WUD/wud.cpp
Normal file
128
src/Cafe/Filesystem/WUD/wud.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "wud.h"
|
||||
#include "Common/filestream.h"
|
||||
|
||||
wud_t* wud_open(const fs::path& path)
|
||||
{
|
||||
FileStream* fs = FileStream::openFile2(path);
|
||||
if( !fs )
|
||||
return nullptr;
|
||||
// allocate wud struct
|
||||
wud_t* wud = (wud_t*)malloc(sizeof(wud_t));
|
||||
memset(wud, 0x00, sizeof(wud_t));
|
||||
wud->fs = fs;
|
||||
// get size of file
|
||||
long long inputFileSize = wud->fs->GetSize();
|
||||
// determine whether the WUD is compressed or not
|
||||
wuxHeader_t wuxHeader = {0};
|
||||
if( wud->fs->readData(&wuxHeader, sizeof(wuxHeader_t)) != sizeof(wuxHeader_t))
|
||||
{
|
||||
// file is too short to be either
|
||||
wud_close(wud);
|
||||
return nullptr;
|
||||
}
|
||||
if( wuxHeader.magic0 == WUX_MAGIC_0 && wuxHeader.magic1 == WUX_MAGIC_1 )
|
||||
{
|
||||
// this is a WUX file
|
||||
wud->isCompressed = true;
|
||||
wud->sectorSize = wuxHeader.sectorSize;
|
||||
wud->uncompressedSize = wuxHeader.uncompressedSize;
|
||||
// validate header values
|
||||
if( wud->sectorSize < 0x100 || wud->sectorSize >= 0x10000000 )
|
||||
{
|
||||
wud_close(wud);
|
||||
return nullptr;
|
||||
}
|
||||
// calculate offsets and sizes
|
||||
wud->indexTableEntryCount = (unsigned int)((wud->uncompressedSize+(long long)(wud->sectorSize-1)) / (long long)wud->sectorSize);
|
||||
wud->offsetIndexTable = sizeof(wuxHeader_t);
|
||||
wud->offsetSectorArray = (wud->offsetIndexTable + (long long)wud->indexTableEntryCount*sizeof(unsigned int));
|
||||
// align to SECTOR_SIZE
|
||||
wud->offsetSectorArray = (wud->offsetSectorArray + (long long)(wud->sectorSize-1));
|
||||
wud->offsetSectorArray = wud->offsetSectorArray - (wud->offsetSectorArray%(long long)wud->sectorSize);
|
||||
// read index table
|
||||
unsigned int indexTableSize = sizeof(unsigned int) * wud->indexTableEntryCount;
|
||||
wud->indexTable = (unsigned int*)malloc(indexTableSize);
|
||||
wud->fs->SetPosition(wud->offsetIndexTable);
|
||||
if( wud->fs->readData(wud->indexTable, indexTableSize) != indexTableSize )
|
||||
{
|
||||
// could not read index table
|
||||
wud_close(wud);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// uncompressed file
|
||||
wud->uncompressedSize = inputFileSize;
|
||||
}
|
||||
return wud;
|
||||
}
|
||||
|
||||
void wud_close(wud_t* wud)
|
||||
{
|
||||
delete wud->fs;
|
||||
if( wud->indexTable )
|
||||
free(wud->indexTable);
|
||||
free(wud);
|
||||
}
|
||||
|
||||
bool wud_isWUXCompressed(wud_t* wud)
|
||||
{
|
||||
return wud->isCompressed;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read data from WUD file
|
||||
* Can read up to 4GB at once
|
||||
*/
|
||||
unsigned int wud_readData(wud_t* wud, void* buffer, unsigned int length, long long offset)
|
||||
{
|
||||
// make sure there is no out-of-bounds read
|
||||
long long fileBytesLeft = wud->uncompressedSize - offset;
|
||||
if( fileBytesLeft <= 0 )
|
||||
return 0;
|
||||
if( fileBytesLeft < (long long)length )
|
||||
length = (unsigned int)fileBytesLeft;
|
||||
// read data
|
||||
unsigned int readBytes = 0;
|
||||
if( wud->isCompressed == false )
|
||||
{
|
||||
// uncompressed read is straight forward
|
||||
wud->fs->SetPosition(offset);
|
||||
readBytes = (unsigned int)wud->fs->readData(buffer, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// compressed read must be handled on a per-sector level
|
||||
while( length > 0 )
|
||||
{
|
||||
unsigned int sectorOffset = (unsigned int)(offset % (long long)wud->sectorSize);
|
||||
unsigned int remainingSectorBytes = wud->sectorSize - sectorOffset;
|
||||
unsigned int sectorIndex = (unsigned int)(offset / (long long)wud->sectorSize);
|
||||
unsigned int bytesToRead = (remainingSectorBytes<length)?remainingSectorBytes:length; // read only up to the end of the current sector
|
||||
// look up real sector index
|
||||
sectorIndex = wud->indexTable[sectorIndex];
|
||||
wud->fs->SetPosition(wud->offsetSectorArray + (long long)sectorIndex * (long long)wud->sectorSize + (long long)sectorOffset);
|
||||
readBytes += (unsigned int)wud->fs->readData(buffer, bytesToRead);
|
||||
// progress read offset, write pointer and decrease length
|
||||
buffer = (void*)((char*)buffer + bytesToRead);
|
||||
length -= bytesToRead;
|
||||
offset += bytesToRead;
|
||||
|
||||
}
|
||||
}
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the size of the data
|
||||
* For .wud: Size of file
|
||||
* For .wux: Size of uncompressed data
|
||||
*/
|
||||
long long wud_getWUDSize(wud_t* wud)
|
||||
{
|
||||
return wud->uncompressedSize;
|
||||
}
|
34
src/Cafe/Filesystem/WUD/wud.h
Normal file
34
src/Cafe/Filesystem/WUD/wud.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
struct wuxHeader_t
|
||||
{
|
||||
unsigned int magic0;
|
||||
unsigned int magic1;
|
||||
unsigned int sectorSize;
|
||||
unsigned long long uncompressedSize;
|
||||
unsigned int flags;
|
||||
};
|
||||
|
||||
struct wud_t
|
||||
{
|
||||
class FileStream* fs;
|
||||
long long uncompressedSize;
|
||||
bool isCompressed;
|
||||
// data used when compressed
|
||||
unsigned int sectorSize;
|
||||
unsigned int indexTableEntryCount;
|
||||
unsigned int* indexTable;
|
||||
long long offsetIndexTable;
|
||||
long long offsetSectorArray;
|
||||
};
|
||||
|
||||
#define WUX_MAGIC_0 '0XUW' // "WUX0"
|
||||
#define WUX_MAGIC_1 0x1099d02e
|
||||
|
||||
// wud and wux functions
|
||||
wud_t* wud_open(const fs::path& path); // transparently handles wud and wux files
|
||||
void wud_close(wud_t* wud);
|
||||
|
||||
bool wud_isWUXCompressed(wud_t* wud);
|
||||
unsigned int wud_readData(wud_t* wud, void* buffer, unsigned int length, long long offset);
|
||||
long long wud_getWUDSize(wud_t* wud);
|
801
src/Cafe/Filesystem/fsc.cpp
Normal file
801
src/Cafe/Filesystem/fsc.cpp
Normal 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();
|
||||
}
|
219
src/Cafe/Filesystem/fsc.h
Normal file
219
src/Cafe/Filesystem/fsc.h
Normal file
|
@ -0,0 +1,219 @@
|
|||
#pragma once
|
||||
|
||||
struct FSCVirtualFile;
|
||||
|
||||
#define FSC_TYPE_INVALID (0)
|
||||
#define FSC_TYPE_FILE (1)
|
||||
#define FSC_TYPE_DIRECTORY (2)
|
||||
|
||||
#define FSC_QUERY_SIZE (1) // file size, 0 for directories
|
||||
#define FSC_QUERY_WRITEABLE (2) // non-zero if file is writeable, else 0
|
||||
|
||||
enum class FSC_ACCESS_FLAG : uint8
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
// file permissions
|
||||
READ_PERMISSION = (1<<0),
|
||||
WRITE_PERMISSION = (1<<1),
|
||||
|
||||
// file open mode (incompatible with OPEN_DIR flag)
|
||||
FILE_ALLOW_CREATE = (1 << 2), // create file if it does not exist
|
||||
FILE_ALWAYS_CREATE = (1 << 3), // overwrite any existing file
|
||||
|
||||
// which types can be opened
|
||||
// invalid operation if neither is set
|
||||
OPEN_DIR = (1 << 4),
|
||||
OPEN_FILE = (1 << 5)
|
||||
};
|
||||
DEFINE_ENUM_FLAG_OPERATORS(FSC_ACCESS_FLAG);
|
||||
|
||||
#define FSC_STATUS_UNDEFINED (-1)
|
||||
#define FSC_STATUS_OK (0)
|
||||
#define FSC_STATUS_INVALID_PATH (1)
|
||||
#define FSC_STATUS_FILE_NOT_FOUND (2)
|
||||
#define FSC_STATUS_ALREADY_EXISTS (3)
|
||||
// note: Unlike the native Wii U filesystem, FSC does not provide separate error codes for NOT_A_FILE and NOT_A_DIRECTORY
|
||||
// to determine them manually, open with both modes (file and dir) and check the type
|
||||
|
||||
#define FSC_MAX_DIR_NAME_LENGTH (256)
|
||||
#define FSC_MAX_DEVICE_PATH_LENGTH ((std::max)(260,FSA_PATH_SIZE_MAX)) // max length for FSC device paths (should be at least equal or greater than supported by host filesystem)
|
||||
|
||||
struct FSCDirEntry
|
||||
{
|
||||
char path[FSC_MAX_DIR_NAME_LENGTH];
|
||||
// stats
|
||||
bool isDirectory;
|
||||
bool isFile;
|
||||
uint32 fileSize;
|
||||
|
||||
std::string_view GetPath()
|
||||
{
|
||||
size_t len = strnlen(path, FSC_MAX_DIR_NAME_LENGTH);
|
||||
return std::basic_string_view<char>(path, len);
|
||||
}
|
||||
};
|
||||
|
||||
class fscDeviceC
|
||||
{
|
||||
public:
|
||||
virtual FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual bool fscDeviceCreateDir(std::wstring_view path, void* ctx, sint32* fscStatus)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool fscDeviceRemoveFileOrDir(std::wstring_view path, void* ctx, sint32* fscStatus)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool fscDeviceRename(std::wstring_view srcPath, std::wstring_view dstPath, void* ctx, sint32* fscStatus)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct FSCVirtualFile
|
||||
{
|
||||
struct FSCDirIteratorState
|
||||
{
|
||||
sint32 index;
|
||||
std::vector<FSCDirEntry> dirEntries;
|
||||
};
|
||||
|
||||
FSCVirtualFile()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
virtual ~FSCVirtualFile()
|
||||
{
|
||||
if (dirIterator)
|
||||
delete dirIterator;
|
||||
}
|
||||
|
||||
virtual sint32 fscGetType()
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual uint64 fscQueryValueU64(uint32 id)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual uint32 fscWriteData(void* buffer, uint32 size)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual uint32 fscReadData(void* buffer, uint32 size)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void fscSetSeek(uint64 seek)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
|
||||
virtual uint64 fscGetSeek()
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void fscSetFileLength(uint64 endOffset)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
|
||||
virtual bool fscDirNext(FSCDirEntry* dirEntry)
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
return false;
|
||||
}
|
||||
|
||||
FSCDirIteratorState* dirIterator{};
|
||||
};
|
||||
|
||||
#define FSC_PRIORITY_BASE (0)
|
||||
#define FSC_PRIORITY_AOC (1)
|
||||
#define FSC_PRIORITY_PATCH (2)
|
||||
#define FSC_PRIORITY_REDIRECT (3)
|
||||
#define FSC_PRIORITY_MAX (3)
|
||||
|
||||
#define FSC_PRIORITY_COUNT (4)
|
||||
|
||||
void fsc_init();
|
||||
sint32 fsc_mount(const char* mountPath, const wchar_t* targetPath, fscDeviceC* fscDevice, void* ctx, sint32 priority=0);
|
||||
bool fsc_unmount(const char* mountPath, sint32 priority);
|
||||
void fsc_unmountAll();
|
||||
|
||||
FSCVirtualFile* fsc_open(const char* path, FSC_ACCESS_FLAG accessFlags, sint32* fscStatus, sint32 maxPriority=FSC_PRIORITY_MAX);
|
||||
FSCVirtualFile* fsc_openDirIterator(const char* path, sint32* fscStatus);
|
||||
bool fsc_createDir(char* path, sint32* fscStatus);
|
||||
bool fsc_rename(char* srcPath, char* dstPath, sint32* fscStatus);
|
||||
bool fsc_remove(char* path, sint32* fscStatus);
|
||||
bool fsc_nextDir(FSCVirtualFile* fscFile, FSCDirEntry* dirEntry);
|
||||
void fsc_close(FSCVirtualFile* fscFile);
|
||||
uint32 fsc_getFileSize(FSCVirtualFile* fscFile);
|
||||
uint32 fsc_getFileSeek(FSCVirtualFile* fscFile);
|
||||
void fsc_setFileSeek(FSCVirtualFile* fscFile, uint32 newSeek);
|
||||
void fsc_setFileLength(FSCVirtualFile* fscFile, uint32 newEndOffset);
|
||||
bool fsc_isDirectory(FSCVirtualFile* fscFile);
|
||||
bool fsc_isFile(FSCVirtualFile* fscFile);
|
||||
bool fsc_isWritable(FSCVirtualFile* fscFile);
|
||||
uint32 fsc_readFile(FSCVirtualFile* fscFile, void* buffer, uint32 size);
|
||||
uint32 fsc_writeFile(FSCVirtualFile* fscFile, void* buffer, uint32 size);
|
||||
|
||||
uint8* fsc_extractFile(const char* path, uint32* fileSize, sint32 maxPriority = FSC_PRIORITY_MAX);
|
||||
std::optional<std::vector<uint8>> fsc_extractFile(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX);
|
||||
bool fsc_doesFileExist(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX);
|
||||
bool fsc_doesDirectoryExist(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX);
|
||||
|
||||
// wud device
|
||||
bool FSCDeviceWUD_Mount(const char* mountPath, std::string_view destinationBaseDir, class FSTVolume* mountedVolume, sint32 priority);
|
||||
|
||||
// wua device
|
||||
bool FSCDeviceWUA_Mount(const char* mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority);
|
||||
|
||||
// hostFS device
|
||||
void fscDeviceHostFS_mapBaseDirectories_deprecated();
|
||||
bool FSCDeviceHostFS_Mount(const char* mountPath, const wchar_t* hostFSPath, sint32 priority);
|
||||
|
||||
// redirect device
|
||||
void fscDeviceRedirect_map();
|
||||
void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority);
|
||||
|
||||
|
||||
// Old path parser helper functions
|
||||
// Replace with FSCPath
|
||||
|
||||
#define FSC_PARSED_PATH_NODES_MAX (32)
|
||||
|
||||
struct CoreinitFSParsedPath
|
||||
{
|
||||
char pathData[640 + 1];
|
||||
uint16 nodeOffset[FSC_PARSED_PATH_NODES_MAX];
|
||||
sint32 numNodes;
|
||||
};
|
||||
|
||||
void coreinitFS_parsePath(CoreinitFSParsedPath* parsedPath, const char* path);
|
||||
bool coreinitFS_checkNodeName(CoreinitFSParsedPath* parsedPath, sint32 index, const char* name);
|
||||
char* coreinitFS_getNodeName(CoreinitFSParsedPath* parsedPath, sint32 index);
|
306
src/Cafe/Filesystem/fscDeviceHostFS.cpp
Normal file
306
src/Cafe/Filesystem/fscDeviceHostFS.cpp
Normal file
|
@ -0,0 +1,306 @@
|
|||
#include "config/ActiveSettings.h"
|
||||
#include "Cafe/Filesystem/fsc.h"
|
||||
#include "Cafe/Filesystem/fscDeviceHostFS.h"
|
||||
|
||||
#include "Common/filestream.h"
|
||||
|
||||
/* FSCVirtualFile implementation for HostFS */
|
||||
|
||||
FSCVirtualFile_Host::~FSCVirtualFile_Host()
|
||||
{
|
||||
if (m_type == FSC_TYPE_FILE)
|
||||
delete m_fs;
|
||||
}
|
||||
|
||||
sint32 FSCVirtualFile_Host::fscGetType()
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
uint32 FSCVirtualFile_Host::fscDeviceHostFSFile_getFileSize()
|
||||
{
|
||||
if (m_type == FSC_TYPE_FILE)
|
||||
{
|
||||
if (m_fileSize > 0xFFFFFFFFULL)
|
||||
cemu_assert_suspicious(); // files larger than 4GB are not supported by Wii U filesystem
|
||||
return (uint32)m_fileSize;
|
||||
}
|
||||
else if (m_type == FSC_TYPE_DIRECTORY)
|
||||
{
|
||||
// todo
|
||||
return (uint32)0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64 FSCVirtualFile_Host::fscQueryValueU64(uint32 id)
|
||||
{
|
||||
if (m_type == FSC_TYPE_FILE)
|
||||
{
|
||||
if (id == FSC_QUERY_SIZE)
|
||||
return fscDeviceHostFSFile_getFileSize();
|
||||
else if (id == FSC_QUERY_WRITEABLE)
|
||||
return m_isWritable;
|
||||
else
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
else if (m_type == FSC_TYPE_DIRECTORY)
|
||||
{
|
||||
if (id == FSC_QUERY_SIZE)
|
||||
return fscDeviceHostFSFile_getFileSize();
|
||||
else
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
cemu_assert_unimplemented();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 FSCVirtualFile_Host::fscWriteData(void* buffer, uint32 size)
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
if (size >= (2UL * 1024UL * 1024UL * 1024UL))
|
||||
{
|
||||
cemu_assert_suspicious();
|
||||
return 0;
|
||||
}
|
||||
sint32 writtenBytes = m_fs->writeData(buffer, (sint32)size);
|
||||
m_seek += (uint64)writtenBytes;
|
||||
m_fileSize = std::max(m_fileSize, m_seek);
|
||||
return (uint32)writtenBytes;
|
||||
}
|
||||
|
||||
uint32 FSCVirtualFile_Host::fscReadData(void* buffer, uint32 size)
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
if (size >= (2UL * 1024UL * 1024UL * 1024UL))
|
||||
{
|
||||
cemu_assert_suspicious();
|
||||
return 0;
|
||||
}
|
||||
uint32 bytesLeft = (uint32)(m_fileSize - m_seek);
|
||||
bytesLeft = std::min(bytesLeft, 0x7FFFFFFFu);
|
||||
sint32 bytesToRead = std::min(bytesLeft, size);
|
||||
uint32 bytesRead = m_fs->readData(buffer, bytesToRead);
|
||||
m_seek += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
void FSCVirtualFile_Host::fscSetSeek(uint64 seek)
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return;
|
||||
this->m_seek = seek;
|
||||
cemu_assert_debug(seek <= m_fileSize);
|
||||
m_fs->SetPosition(seek);
|
||||
}
|
||||
|
||||
uint64 FSCVirtualFile_Host::fscGetSeek()
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
return m_seek;
|
||||
}
|
||||
|
||||
void FSCVirtualFile_Host::fscSetFileLength(uint64 endOffset)
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return;
|
||||
m_fs->SetPosition(endOffset);
|
||||
bool r = m_fs->SetEndOfFile();
|
||||
m_seek = std::min(m_seek, endOffset);
|
||||
m_fileSize = m_seek;
|
||||
m_fs->SetPosition(m_seek);
|
||||
if (!r)
|
||||
cemuLog_force("fscSetFileLength: Failed to set size to 0x{:x}", endOffset);
|
||||
}
|
||||
|
||||
bool FSCVirtualFile_Host::fscDirNext(FSCDirEntry* dirEntry)
|
||||
{
|
||||
if (m_type != FSC_TYPE_DIRECTORY)
|
||||
return false;
|
||||
|
||||
if (!m_dirIterator)
|
||||
{
|
||||
// init iterator on first iteration attempt
|
||||
m_dirIterator.reset(new fs::directory_iterator(*m_path));
|
||||
if (!m_dirIterator)
|
||||
{
|
||||
cemuLog_force("Failed to iterate directory: {}", _utf8Wrapper(m_path->generic_u8string()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (*m_dirIterator == fs::end(*m_dirIterator))
|
||||
return false;
|
||||
|
||||
const fs::directory_entry& entry = **m_dirIterator;
|
||||
|
||||
std::string fileName = entry.path().filename().generic_string();
|
||||
if (fileName.size() >= sizeof(dirEntry->path) - 1)
|
||||
fileName.resize(sizeof(dirEntry->path) - 1);
|
||||
strncpy(dirEntry->path, fileName.data(), sizeof(dirEntry->path));
|
||||
if (entry.is_directory())
|
||||
{
|
||||
dirEntry->isDirectory = true;
|
||||
dirEntry->isFile = false;
|
||||
dirEntry->fileSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
dirEntry->isDirectory = false;
|
||||
dirEntry->isFile = true;
|
||||
dirEntry->fileSize = entry.file_size();
|
||||
}
|
||||
|
||||
(*m_dirIterator)++;
|
||||
return true;
|
||||
}
|
||||
|
||||
FSCVirtualFile* FSCVirtualFile_Host::OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus)
|
||||
{
|
||||
if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && !HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||
cemu_assert_debug(false); // not allowed. At least one of both flags must be set
|
||||
|
||||
// attempt to open as file
|
||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
|
||||
{
|
||||
FileStream* fs{};
|
||||
bool writeAccessRequested = HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION);
|
||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALLOW_CREATE))
|
||||
{
|
||||
fs = FileStream::openFile2(path, writeAccessRequested);
|
||||
if (!fs)
|
||||
{
|
||||
cemu_assert_debug(writeAccessRequested);
|
||||
fs = FileStream::createFile2(path);
|
||||
if (!fs)
|
||||
cemuLog_force("FSC: File create failed for {}", _utf8Wrapper(path));
|
||||
}
|
||||
}
|
||||
else if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE))
|
||||
{
|
||||
fs = FileStream::createFile2(path);
|
||||
if (!fs)
|
||||
cemuLog_force("FSC: File create failed for {}", _utf8Wrapper(path));
|
||||
}
|
||||
else
|
||||
{
|
||||
fs = FileStream::openFile2(path, writeAccessRequested);
|
||||
}
|
||||
if (fs)
|
||||
{
|
||||
FSCVirtualFile_Host* vf = new FSCVirtualFile_Host(FSC_TYPE_FILE);
|
||||
vf->m_fs = fs;
|
||||
vf->m_isWritable = writeAccessRequested;
|
||||
vf->m_fileSize = fs->GetSize();
|
||||
fscStatus = FSC_STATUS_OK;
|
||||
return vf;
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to open as directory
|
||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||
{
|
||||
std::error_code ec;
|
||||
bool isExistingDir = fs::is_directory(path, ec);
|
||||
if (isExistingDir)
|
||||
{
|
||||
FSCVirtualFile_Host* vf = new FSCVirtualFile_Host(FSC_TYPE_DIRECTORY);
|
||||
vf->m_path.reset(new std::filesystem::path(path));
|
||||
fscStatus = FSC_STATUS_OK;
|
||||
return vf;
|
||||
}
|
||||
}
|
||||
fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Device implementation */
|
||||
|
||||
class fscDeviceHostFSC : public fscDeviceC
|
||||
{
|
||||
public:
|
||||
FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
FSCVirtualFile* vf = FSCVirtualFile_Host::OpenFile(path, accessFlags, *fscStatus);
|
||||
cemu_assert_debug((bool)vf == (*fscStatus == FSC_STATUS_OK));
|
||||
return vf;
|
||||
}
|
||||
|
||||
bool fscDeviceCreateDir(std::wstring_view path, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
fs::path dirPath(path);
|
||||
if (fs::exists(path))
|
||||
{
|
||||
if (!fs::is_directory(dirPath))
|
||||
cemuLog_force("CreateDir: {} already exists but is not a directory", _utf8Wrapper(dirPath));
|
||||
*fscStatus = FSC_STATUS_ALREADY_EXISTS;
|
||||
return false;
|
||||
}
|
||||
std::error_code ec;
|
||||
bool r = fs::create_directories(dirPath, ec);
|
||||
if(!r)
|
||||
cemuLog_force("CreateDir: Failed to create {}", _utf8Wrapper(dirPath));
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fscDeviceRemoveFileOrDir(std::wstring_view path, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
fs::path _path(path);
|
||||
std::error_code ec;
|
||||
if (!fs::exists(_path, ec))
|
||||
{
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return false;
|
||||
}
|
||||
if (!fs::remove(_path, ec))
|
||||
{
|
||||
cemu_assert_unimplemented(); // return correct error (e.g. if directory is non-empty)
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
}
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fscDeviceRename(std::wstring_view srcPath, std::wstring_view dstPath, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
fs::path _srcPath(srcPath);
|
||||
fs::path _dstPath(dstPath);
|
||||
std::error_code ec;
|
||||
if (!fs::exists(_srcPath, ec))
|
||||
{
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return false;
|
||||
}
|
||||
fs::rename(_srcPath, _dstPath, ec);
|
||||
return true;
|
||||
}
|
||||
|
||||
// singleton
|
||||
public:
|
||||
static fscDeviceHostFSC& instance()
|
||||
{
|
||||
static fscDeviceHostFSC _instance;
|
||||
return _instance;
|
||||
}
|
||||
};
|
||||
|
||||
void fscDeviceHostFS_mapBaseDirectories_deprecated()
|
||||
{
|
||||
const auto mlc = ActiveSettings::GetMlcPath();
|
||||
fsc_mount("/cemuBossStorage/", (mlc / "usr/boss/").generic_wstring().c_str(), &fscDeviceHostFSC::instance(), NULL, FSC_PRIORITY_BASE);
|
||||
fsc_mount("/vol/storage_mlc01/", (mlc / "").generic_wstring().c_str(), &fscDeviceHostFSC::instance(), NULL, FSC_PRIORITY_BASE);
|
||||
}
|
||||
|
||||
bool FSCDeviceHostFS_Mount(const char* mountPath, const wchar_t* hostFSPath, sint32 priority)
|
||||
{
|
||||
std::wstring hostTargetPath(hostFSPath);
|
||||
if (!hostTargetPath.empty() && (hostTargetPath.back() != '/' && hostTargetPath.back() != '\\'))
|
||||
hostTargetPath.push_back('/');
|
||||
return fsc_mount(mountPath, hostTargetPath.c_str(), &fscDeviceHostFSC::instance(), nullptr, priority) == FSC_STATUS_OK;
|
||||
}
|
34
src/Cafe/Filesystem/fscDeviceHostFS.h
Normal file
34
src/Cafe/Filesystem/fscDeviceHostFS.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include "Cafe/Filesystem/fsc.h"
|
||||
|
||||
class FSCVirtualFile_Host : public FSCVirtualFile
|
||||
{
|
||||
public:
|
||||
static FSCVirtualFile* OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus);
|
||||
~FSCVirtualFile_Host() override;
|
||||
|
||||
sint32 fscGetType() override;
|
||||
|
||||
uint32 fscDeviceHostFSFile_getFileSize();
|
||||
|
||||
uint64 fscQueryValueU64(uint32 id) override;
|
||||
uint32 fscWriteData(void* buffer, uint32 size) override;
|
||||
uint32 fscReadData(void* buffer, uint32 size) override;
|
||||
void fscSetSeek(uint64 seek) override;
|
||||
uint64 fscGetSeek() override;
|
||||
void fscSetFileLength(uint64 endOffset) override;
|
||||
bool fscDirNext(FSCDirEntry* dirEntry) override;
|
||||
|
||||
private:
|
||||
FSCVirtualFile_Host(uint32 type) : m_type(type) {};
|
||||
|
||||
private:
|
||||
uint32 m_type; // FSC_TYPE_*
|
||||
class FileStream* m_fs{};
|
||||
// file
|
||||
uint64 m_seek{ 0 };
|
||||
uint64 m_fileSize{ 0 };
|
||||
bool m_isWritable{ false };
|
||||
// directory
|
||||
std::unique_ptr<std::filesystem::path> m_path{};
|
||||
std::unique_ptr<std::filesystem::directory_iterator> m_dirIterator{};
|
||||
};
|
57
src/Cafe/Filesystem/fscDeviceRedirect.cpp
Normal file
57
src/Cafe/Filesystem/fscDeviceRedirect.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#include "util/helpers/helpers.h"
|
||||
#include "Cafe/Filesystem/fscDeviceHostFS.h"
|
||||
#include "FST/fstUtil.h"
|
||||
|
||||
struct RedirectEntry
|
||||
{
|
||||
RedirectEntry(const fs::path& dstPath, sint32 priority) : dstPath(dstPath), priority(priority) {}
|
||||
fs::path dstPath;
|
||||
sint32 priority;
|
||||
};
|
||||
|
||||
FileTree<RedirectEntry, false> redirectTree;
|
||||
|
||||
void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority)
|
||||
{
|
||||
std::wstring virtualSourcePathW = boost::nowide::widen(std::string(virtualSourcePath));
|
||||
// check if source already has a redirection
|
||||
RedirectEntry* existingEntry;
|
||||
if (redirectTree.getFile(virtualSourcePathW, existingEntry))
|
||||
{
|
||||
if (existingEntry->priority >= priority)
|
||||
return; // dont replace entries with equal or higher priority
|
||||
// unregister existing entry
|
||||
redirectTree.removeFile(virtualSourcePathW.c_str());
|
||||
delete existingEntry;
|
||||
}
|
||||
RedirectEntry* entry = new RedirectEntry(targetFilePath, priority);
|
||||
redirectTree.addFile(virtualSourcePathW.c_str(), entry);
|
||||
}
|
||||
|
||||
class fscDeviceTypeRedirect : public fscDeviceC
|
||||
{
|
||||
FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view pathW, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
RedirectEntry* redirectionEntry;
|
||||
if (redirectTree.getFile(pathW, redirectionEntry))
|
||||
return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
static fscDeviceTypeRedirect& instance()
|
||||
{
|
||||
static fscDeviceTypeRedirect _instance;
|
||||
return _instance;
|
||||
}
|
||||
};
|
||||
|
||||
bool _redirectMapped = false;
|
||||
|
||||
void fscDeviceRedirect_map()
|
||||
{
|
||||
if (_redirectMapped)
|
||||
return;
|
||||
fsc_mount("/", L"/", &fscDeviceTypeRedirect::instance(), nullptr, FSC_PRIORITY_REDIRECT);
|
||||
_redirectMapped = true;
|
||||
}
|
178
src/Cafe/Filesystem/fscDeviceWua.cpp
Normal file
178
src/Cafe/Filesystem/fscDeviceWua.cpp
Normal file
|
@ -0,0 +1,178 @@
|
|||
#include "Cafe/Filesystem/fsc.h"
|
||||
#include <zarchive/zarchivereader.h>
|
||||
|
||||
class FSCDeviceWuaFileCtx : public FSCVirtualFile
|
||||
{
|
||||
friend class fscDeviceWUAC;
|
||||
|
||||
protected:
|
||||
FSCDeviceWuaFileCtx(ZArchiveReader* archive, ZArchiveNodeHandle fstFileHandle, uint32 fscType)
|
||||
{
|
||||
this->m_archive = archive;
|
||||
this->m_fscType = fscType;
|
||||
this->m_nodeHandle = fstFileHandle;
|
||||
this->m_seek = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
sint32 fscGetType() override
|
||||
{
|
||||
return m_fscType;
|
||||
}
|
||||
|
||||
uint32 fscDeviceWuaFile_getFileSize()
|
||||
{
|
||||
return (uint32)m_archive->GetFileSize(m_nodeHandle);
|
||||
}
|
||||
|
||||
uint64 fscQueryValueU64(uint32 id) override
|
||||
{
|
||||
if (m_fscType == FSC_TYPE_FILE)
|
||||
{
|
||||
if (id == FSC_QUERY_SIZE)
|
||||
return fscDeviceWuaFile_getFileSize();
|
||||
else if (id == FSC_QUERY_WRITEABLE)
|
||||
return 0; // WUD images are read-only
|
||||
else
|
||||
cemu_assert_error();
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 fscWriteData(void* buffer, uint32 size) override
|
||||
{
|
||||
cemu_assert_error();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 fscReadData(void* buffer, uint32 size) override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
cemu_assert(size < (2ULL * 1024 * 1024 * 1024)); // single read operation larger than 2GiB not supported
|
||||
uint32 bytesLeft = fscDeviceWuaFile_getFileSize() - m_seek;
|
||||
uint32 bytesToRead = (std::min)(bytesLeft, (uint32)size);
|
||||
uint32 bytesSuccessfullyRead = (uint32)m_archive->ReadFromFile(m_nodeHandle, m_seek, bytesToRead, buffer);
|
||||
m_seek += bytesSuccessfullyRead;
|
||||
return bytesSuccessfullyRead;
|
||||
}
|
||||
|
||||
void fscSetSeek(uint64 seek) override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_FILE)
|
||||
return;
|
||||
cemu_assert_debug(seek <= 0xFFFFFFFFULL);
|
||||
this->m_seek = (uint32)seek;
|
||||
}
|
||||
|
||||
uint64 fscGetSeek() override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
return m_seek;
|
||||
}
|
||||
|
||||
bool fscDirNext(FSCDirEntry* dirEntry) override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_DIRECTORY)
|
||||
return false;
|
||||
|
||||
ZArchiveReader::DirEntry zarDirEntry;
|
||||
if (!m_archive->GetDirEntry(m_nodeHandle, m_iteratorIndex, zarDirEntry))
|
||||
return false;
|
||||
m_iteratorIndex++;
|
||||
|
||||
if (zarDirEntry.isDirectory)
|
||||
{
|
||||
dirEntry->isDirectory = true;
|
||||
dirEntry->isFile = false;
|
||||
dirEntry->fileSize = 0;
|
||||
}
|
||||
else if(zarDirEntry.isFile)
|
||||
{
|
||||
dirEntry->isDirectory = false;
|
||||
dirEntry->isFile = true;
|
||||
dirEntry->fileSize = (uint32)zarDirEntry.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_suspicious();
|
||||
}
|
||||
std::memset(dirEntry->path, 0, sizeof(dirEntry->path));
|
||||
std::strncpy(dirEntry->path, zarDirEntry.name.data(), std::min(sizeof(dirEntry->path) - 1, zarDirEntry.name.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
ZArchiveReader* m_archive{nullptr};
|
||||
sint32 m_fscType;
|
||||
ZArchiveNodeHandle m_nodeHandle;
|
||||
// file
|
||||
uint32 m_seek{0};
|
||||
// directory
|
||||
uint32 m_iteratorIndex{0};
|
||||
};
|
||||
|
||||
class fscDeviceWUAC : public fscDeviceC
|
||||
{
|
||||
FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
ZArchiveReader* archive = (ZArchiveReader*)ctx;
|
||||
cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUA is not supported
|
||||
|
||||
std::string pathU8 = boost::nowide::narrow(path.data(), path.size());
|
||||
|
||||
ZArchiveNodeHandle fileHandle = archive->LookUp(pathU8, true, true);
|
||||
if (fileHandle == ZARCHIVE_INVALID_NODE)
|
||||
{
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
if (archive->IsFile(fileHandle))
|
||||
{
|
||||
if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
|
||||
{
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
return new FSCDeviceWuaFileCtx(archive, fileHandle, FSC_TYPE_FILE);
|
||||
}
|
||||
else if (archive->IsDirectory(fileHandle))
|
||||
{
|
||||
if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||
{
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
return new FSCDeviceWuaFileCtx(archive, fileHandle, FSC_TYPE_DIRECTORY);
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_suspicious();
|
||||
}
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// singleton
|
||||
public:
|
||||
static fscDeviceWUAC& instance()
|
||||
{
|
||||
static fscDeviceWUAC _instance;
|
||||
return _instance;
|
||||
}
|
||||
};
|
||||
|
||||
bool FSCDeviceWUA_Mount(const char* mountPath, std::string_view destinationBaseDir, ZArchiveReader* archive, sint32 priority)
|
||||
{
|
||||
std::wstring hostTargetPath(boost::nowide::widen(destinationBaseDir.data(), destinationBaseDir.size()));
|
||||
if (!hostTargetPath.empty() && (hostTargetPath.back() != '/' && hostTargetPath.back() != '\\'))
|
||||
hostTargetPath.push_back('/');
|
||||
return fsc_mount(mountPath, hostTargetPath.c_str(), &fscDeviceWUAC::instance(), archive, priority) == FSC_STATUS_OK;
|
||||
}
|
166
src/Cafe/Filesystem/fscDeviceWud.cpp
Normal file
166
src/Cafe/Filesystem/fscDeviceWud.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include "Cafe/Filesystem/fsc.h"
|
||||
#include "Cafe/Filesystem/FST/FST.h"
|
||||
|
||||
class FSCDeviceWudFileCtx : public FSCVirtualFile
|
||||
{
|
||||
friend class fscDeviceWUDC;
|
||||
|
||||
protected:
|
||||
FSCDeviceWudFileCtx(FSTVolume* _volume, FSTFileHandle _fstFileHandle)
|
||||
{
|
||||
this->m_volume = _volume;
|
||||
this->m_fscType = FSC_TYPE_FILE;
|
||||
this->m_fstFileHandle = _fstFileHandle;
|
||||
this->m_seek = 0;
|
||||
};
|
||||
|
||||
FSCDeviceWudFileCtx(FSTVolume* _volume, FSTDirectoryIterator _dirIterator)
|
||||
{
|
||||
this->m_volume = _volume;
|
||||
this->m_fscType = FSC_TYPE_DIRECTORY;
|
||||
this->m_dirIterator = _dirIterator;
|
||||
}
|
||||
|
||||
public:
|
||||
sint32 fscGetType() override
|
||||
{
|
||||
return m_fscType;
|
||||
}
|
||||
|
||||
uint32 fscDeviceWudFile_getFileSize()
|
||||
{
|
||||
return m_volume->GetFileSize(m_fstFileHandle);
|
||||
}
|
||||
|
||||
uint64 fscQueryValueU64(uint32 id) override
|
||||
{
|
||||
if (m_fscType == FSC_TYPE_FILE)
|
||||
{
|
||||
if (id == FSC_QUERY_SIZE)
|
||||
return fscDeviceWudFile_getFileSize();
|
||||
else if (id == FSC_QUERY_WRITEABLE)
|
||||
return 0; // WUD images are read-only
|
||||
else
|
||||
cemu_assert_error();
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 fscWriteData(void* buffer, uint32 size) override
|
||||
{
|
||||
cemu_assert_error();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 fscReadData(void* buffer, uint32 size) override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
cemu_assert(size < (2ULL * 1024 * 1024 * 1024)); // single read operation larger than 2GiB not supported
|
||||
uint32 bytesLeft = fscDeviceWudFile_getFileSize() - m_seek;
|
||||
uint32 bytesToRead = (std::min)(bytesLeft, (uint32)size);
|
||||
uint32 bytesSuccessfullyRead = m_volume->ReadFile(m_fstFileHandle, m_seek, bytesToRead, buffer);
|
||||
m_seek += bytesSuccessfullyRead;
|
||||
return bytesSuccessfullyRead;
|
||||
}
|
||||
|
||||
void fscSetSeek(uint64 seek) override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_FILE)
|
||||
return;
|
||||
cemu_assert_debug(seek <= 0xFFFFFFFFULL);
|
||||
this->m_seek = (uint32)seek;
|
||||
}
|
||||
|
||||
uint64 fscGetSeek() override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
return m_seek;
|
||||
}
|
||||
|
||||
bool fscDirNext(FSCDirEntry* dirEntry) override
|
||||
{
|
||||
if (m_fscType != FSC_TYPE_DIRECTORY)
|
||||
return false;
|
||||
FSTFileHandle entryItr;
|
||||
if (!m_volume->Next(m_dirIterator, entryItr))
|
||||
return false;
|
||||
if (m_volume->IsDirectory(entryItr))
|
||||
{
|
||||
dirEntry->isDirectory = true;
|
||||
dirEntry->isFile = false;
|
||||
dirEntry->fileSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
dirEntry->isDirectory = false;
|
||||
dirEntry->isFile = true;
|
||||
dirEntry->fileSize = m_volume->GetFileSize(entryItr);
|
||||
}
|
||||
auto path = m_volume->GetName(entryItr);
|
||||
std::memset(dirEntry->path, 0, sizeof(dirEntry->path));
|
||||
std::strncpy(dirEntry->path, path.data(), std::min(sizeof(dirEntry->path) - 1, path.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
FSTVolume* m_volume{nullptr};
|
||||
sint32 m_fscType;
|
||||
FSTFileHandle m_fstFileHandle;
|
||||
// file
|
||||
uint32 m_seek{0};
|
||||
// directory
|
||||
FSTDirectoryIterator m_dirIterator{};
|
||||
};
|
||||
|
||||
class fscDeviceWUDC : public fscDeviceC
|
||||
{
|
||||
FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
FSTVolume* mountedVolume = (FSTVolume*)ctx;
|
||||
cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to FST is never allowed
|
||||
|
||||
std::string pathU8 = boost::nowide::narrow(path.data(), path.size());
|
||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
|
||||
{
|
||||
FSTFileHandle fstFileHandle;
|
||||
if (mountedVolume->OpenFile(pathU8, fstFileHandle, true))
|
||||
{
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
return new FSCDeviceWudFileCtx(mountedVolume, fstFileHandle);
|
||||
}
|
||||
}
|
||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||
{
|
||||
FSTDirectoryIterator dirIterator;
|
||||
if (mountedVolume->OpenDirectoryIterator(pathU8, dirIterator))
|
||||
{
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
return new FSCDeviceWudFileCtx(mountedVolume, dirIterator);
|
||||
}
|
||||
}
|
||||
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// singleton
|
||||
public:
|
||||
static fscDeviceWUDC& instance()
|
||||
{
|
||||
static fscDeviceWUDC _instance;
|
||||
return _instance;
|
||||
}
|
||||
};
|
||||
|
||||
bool FSCDeviceWUD_Mount(const char* mountPath, std::string_view destinationBaseDir, FSTVolume* mountedVolume, sint32 priority)
|
||||
{
|
||||
std::wstring hostTargetPath(boost::nowide::widen(destinationBaseDir.data(), destinationBaseDir.size()));
|
||||
if (!hostTargetPath.empty() && (hostTargetPath.back() != '/' && hostTargetPath.back() != '\\'))
|
||||
hostTargetPath.push_back('/');
|
||||
return fsc_mount(mountPath, hostTargetPath.c_str(), &fscDeviceWUDC::instance(), mountedVolume, priority) == FSC_STATUS_OK;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue