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

File diff suppressed because it is too large Load diff

View 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);
};

View 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();
}

View file

@ -0,0 +1,5 @@
#pragma once
void KeyCache_Prepare();
uint8* KeyCache_GetAES128(sint32 index);

View 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);
}

View 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;
}

View 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
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();
}

219
src/Cafe/Filesystem/fsc.h Normal file
View 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);

View 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;
}

View 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{};
};

View 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;
}

View 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;
}

View 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;
}