Merge pull request #673 from wheremyfoodat/TWL-Photo

FS: Add the Twilight Zone
This commit is contained in:
wheremyfoodat 2024-12-08 23:06:32 +02:00 committed by GitHub
commit 2e9bbae46d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 299 additions and 146 deletions

View file

@ -338,6 +338,7 @@ set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/cor
set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp
src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp src/core/fs/romfs.cpp src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp src/core/fs/romfs.cpp
src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp src/core/fs/archive_system_save_data.cpp src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp src/core/fs/archive_system_save_data.cpp
src/core/fs/archive_twl_photo.cpp src/core/fs/archive_twl_sound.cpp
) )
set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp
@ -387,7 +388,8 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
include/PICA/pica_vert_config.hpp include/sdl_sensors.hpp include/PICA/draw_acceleration.hpp include/renderdoc.hpp include/PICA/pica_vert_config.hpp include/sdl_sensors.hpp include/PICA/draw_acceleration.hpp include/renderdoc.hpp
include/align.hpp include/audio/aac_decoder.hpp include/PICA/pica_simd.hpp include/services/fonts.hpp include/align.hpp include/audio/aac_decoder.hpp include/PICA/pica_simd.hpp include/services/fonts.hpp
include/audio/audio_interpolation.hpp include/audio/hle_mixer.hpp include/audio/dsp_simd.hpp include/audio/audio_interpolation.hpp include/audio/hle_mixer.hpp include/audio/dsp_simd.hpp
include/services/dsp_firmware_db.hpp include/frontend_settings.hpp include/services/dsp_firmware_db.hpp include/frontend_settings.hpp include/fs/archive_twl_photo.hpp
include/fs/archive_twl_sound.hpp
) )
cmrc_add_resource_library( cmrc_add_resource_library(

View file

@ -7,6 +7,7 @@
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
#include "helpers.hpp" #include "helpers.hpp"
#include "memory.hpp" #include "memory.hpp"
#include "result.hpp" #include "result.hpp"
@ -15,13 +16,13 @@
using Result::HorizonResult; using Result::HorizonResult;
namespace PathType { namespace PathType {
enum : u32 { enum : u32 {
Invalid = 0, Invalid = 0,
Empty = 1, Empty = 1,
Binary = 2, Binary = 2,
ASCII = 3, ASCII = 3,
UTF16 = 4, UTF16 = 4,
}; };
} }
namespace ArchiveID { namespace ArchiveID {
@ -40,85 +41,88 @@ namespace ArchiveID {
UserSaveData1 = 0x567890B2, UserSaveData1 = 0x567890B2,
// 3DBrew: Similar to 0x567890B2 but can only access Accessible Save specified in exheader? // 3DBrew: Similar to 0x567890B2 but can only access Accessible Save specified in exheader?
UserSaveData2 = 0x567890B4, UserSaveData2 = 0x567890B4,
TwlPhoto = 0x567890AC,
TwlSound = 0x567890AD,
}; };
static std::string toString(u32 id) { static std::string toString(u32 id) {
switch (id) { switch (id) {
case SelfNCCH: return "SelfNCCH"; case SelfNCCH: return "SelfNCCH";
case SaveData: return "SaveData"; case SaveData: return "SaveData";
case ExtSaveData: return "ExtSaveData"; case ExtSaveData: return "ExtSaveData";
case SharedExtSaveData: return "SharedExtSaveData"; case SharedExtSaveData: return "SharedExtSaveData";
case SystemSaveData: return "SystemSaveData"; case SystemSaveData: return "SystemSaveData";
case SDMC: return "SDMC"; case SDMC: return "SDMC";
case SDMCWriteOnly: return "SDMC (Write-only)"; case SDMCWriteOnly: return "SDMC (Write-only)";
case SavedataAndNcch: return "Savedata & NCCH (archive 0x2345678A)"; case SavedataAndNcch: return "Savedata & NCCH (archive 0x2345678A)";
default: return "Unknown archive"; case TwlPhoto: return "TWL_PHOTO";
} case TwlSound: return "TWL_SOUND";
} default: return "Unknown archive";
} }
}
} // namespace ArchiveID
struct FSPath { struct FSPath {
u32 type = PathType::Invalid; u32 type = PathType::Invalid;
std::vector<u8> binary; // Path data for binary paths std::vector<u8> binary; // Path data for binary paths
std::string string; // Path data for ASCII paths std::string string; // Path data for ASCII paths
std::u16string utf16_string; std::u16string utf16_string;
FSPath() {} FSPath() {}
FSPath(u32 type, const std::vector<u8>& vec) : type(type) { FSPath(u32 type, const std::vector<u8>& vec) : type(type) {
switch (type) { switch (type) {
case PathType::Binary: case PathType::Binary: binary = std::move(vec); break;
binary = std::move(vec);
break;
case PathType::ASCII: case PathType::ASCII:
string.resize(vec.size() - 1); // -1 because of the null terminator string.resize(vec.size() - 1); // -1 because of the null terminator
std::memcpy(string.data(), vec.data(), vec.size() - 1); // Copy string data std::memcpy(string.data(), vec.data(), vec.size() - 1); // Copy string data
break; break;
case PathType::UTF16: { case PathType::UTF16: {
const size_t size = vec.size() / sizeof(u16) - 1; // Character count. -1 because null terminator here too const size_t size = vec.size() / sizeof(u16) - 1; // Character count. -1 because null terminator here too
utf16_string.resize(size); utf16_string.resize(size);
std::memcpy(utf16_string.data(), vec.data(), size * sizeof(u16)); std::memcpy(utf16_string.data(), vec.data(), size * sizeof(u16));
break; break;
} };
; } }
} }
}; };
struct FilePerms { struct FilePerms {
u32 raw; u32 raw;
FilePerms(u32 val) : raw(val) {} FilePerms(u32 val) : raw(val) {}
bool read() const { return (raw & 1) != 0; } bool read() const { return (raw & 1) != 0; }
bool write() const { return (raw & 2) != 0; } bool write() const { return (raw & 2) != 0; }
bool create() const { return (raw & 4) != 0; } bool create() const { return (raw & 4) != 0; }
}; };
class ArchiveBase; class ArchiveBase;
struct FileSession { struct FileSession {
ArchiveBase* archive = nullptr; ArchiveBase* archive = nullptr;
FILE* fd = nullptr; // File descriptor for file sessions that require them. FILE* fd = nullptr; // File descriptor for file sessions that require them.
FSPath path; FSPath path;
FSPath archivePath; FSPath archivePath;
u32 priority = 0; // TODO: What does this even do u32 priority = 0; // TODO: What does this even do
bool isOpen; bool isOpen;
FileSession(ArchiveBase* archive, const FSPath& filePath, const FSPath& archivePath, FILE* fd, bool isOpen = true) : FileSession(ArchiveBase* archive, const FSPath& filePath, const FSPath& archivePath, FILE* fd, bool isOpen = true)
archive(archive), path(filePath), archivePath(archivePath), fd(fd), isOpen(isOpen), priority(0) {} : archive(archive), path(filePath), archivePath(archivePath), fd(fd), isOpen(isOpen), priority(0) {}
// For cloning a file session // For cloning a file session
FileSession(const FileSession& other) : archive(other.archive), path(other.path), FileSession(const FileSession& other)
archivePath(other.archivePath), fd(other.fd), isOpen(other.isOpen), priority(other.priority) {} : archive(other.archive), path(other.path), archivePath(other.archivePath), fd(other.fd), isOpen(other.isOpen), priority(other.priority) {}
}; };
struct ArchiveSession { struct ArchiveSession {
ArchiveBase* archive = nullptr; ArchiveBase* archive = nullptr;
FSPath path; FSPath path;
bool isOpen; bool isOpen;
ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(filePath), isOpen(isOpen) {} ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(filePath), isOpen(isOpen) {}
}; };
struct DirectoryEntry { struct DirectoryEntry {
@ -156,106 +160,104 @@ struct DirectorySession {
using FileDescriptor = std::optional<FILE*>; using FileDescriptor = std::optional<FILE*>;
class ArchiveBase { class ArchiveBase {
public: public:
struct FormatInfo { struct FormatInfo {
u32 size; // Archive size u32 size; // Archive size
u32 numOfDirectories; // Number of directories u32 numOfDirectories; // Number of directories
u32 numOfFiles; // Number of files u32 numOfFiles; // Number of files
bool duplicateData; // Whether to duplicate data or not bool duplicateData; // Whether to duplicate data or not
}; };
protected: protected:
using Handle = u32; using Handle = u32;
static constexpr FileDescriptor NoFile = nullptr; static constexpr FileDescriptor NoFile = nullptr;
static constexpr FileDescriptor FileError = std::nullopt; static constexpr FileDescriptor FileError = std::nullopt;
Memory& mem; Memory& mem;
// Returns if a specified 3DS path in UTF16 or ASCII format is safe or not // Returns if a specified 3DS path in UTF16 or ASCII format is safe or not
// A 3DS path is considered safe if its first character is '/' which means we're not trying to access anything outside the root of the fs // A 3DS path is considered safe if its first character is '/' which means we're not trying to access anything outside the root of the fs
// And if it doesn't contain enough instances of ".." (Indicating "climb up a folder" in filesystems) to let the software climb up the directory tree // And if it doesn't contain enough instances of ".." (Indicating "climb up a folder" in filesystems) to let the software climb up the directory
// And access files outside of the emulator's app data folder // tree And access files outside of the emulator's app data folder
template <u32 format> template <u32 format>
bool isPathSafe(const FSPath& path) { bool isPathSafe(const FSPath& path) {
static_assert(format == PathType::ASCII || format == PathType::UTF16); static_assert(format == PathType::ASCII || format == PathType::UTF16);
using String = typename std::conditional<format == PathType::UTF16, std::u16string, std::string>::type; // String type for the path using String = typename std::conditional<format == PathType::UTF16, std::u16string, std::string>::type; // String type for the path
using Char = typename String::value_type; // Char type for the path using Char = typename String::value_type; // Char type for the path
String pathString, dots; String pathString, dots;
if constexpr (std::is_same<String, std::u16string>()) { if constexpr (std::is_same<String, std::u16string>()) {
pathString = path.utf16_string; pathString = path.utf16_string;
dots = u".."; dots = u"..";
} else { } else {
pathString = path.string; pathString = path.string;
dots = ".."; dots = "..";
} }
// If the path string doesn't begin with / then that means it's accessing outside the FS root, which is invalid & unsafe // If the path string doesn't begin with / then that means it's accessing outside the FS root, which is invalid & unsafe
if (pathString[0] != Char('/')) return false; if (pathString[0] != Char('/')) return false;
// Counts how many folders sans the root our file is nested under. // Counts how many folders sans the root our file is nested under.
// If it's < 0 at any point of parsing, then the path is unsafe and tries to crawl outside our file sandbox. // If it's < 0 at any point of parsing, then the path is unsafe and tries to crawl outside our file sandbox.
// If it's 0 then this is the FS root. // If it's 0 then this is the FS root.
// If it's > 0 then we're in a subdirectory of the root. // If it's > 0 then we're in a subdirectory of the root.
int level = 0; int level = 0;
// Split the string on / characters and see how many of the substrings are ".." // Split the string on / characters and see how many of the substrings are ".."
size_t pos = 0; size_t pos = 0;
while ((pos = pathString.find(Char('/'))) != String::npos) { while ((pos = pathString.find(Char('/'))) != String::npos) {
String token = pathString.substr(0, pos); String token = pathString.substr(0, pos);
pathString.erase(0, pos + 1); pathString.erase(0, pos + 1);
if (token == dots) { if (token == dots) {
level--; level--;
if (level < 0) return false; if (level < 0) return false;
} else { } else {
level++; level++;
} }
} }
return true; return true;
} }
public: public:
virtual std::string name() = 0; virtual std::string name() = 0;
virtual u64 getFreeBytes() = 0; virtual u64 getFreeBytes() = 0;
virtual HorizonResult createFile(const FSPath& path, u64 size) = 0; virtual HorizonResult createFile(const FSPath& path, u64 size) = 0;
virtual HorizonResult deleteFile(const FSPath& path) = 0; virtual HorizonResult deleteFile(const FSPath& path) = 0;
virtual Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) { virtual Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) {
Helpers::panic("Unimplemented GetFormatInfo for %s archive", name().c_str()); Helpers::panic("Unimplemented GetFormatInfo for %s archive", name().c_str());
// Return a dummy struct just to avoid the UB of not returning anything, even if we panic // Return a dummy struct just to avoid the UB of not returning anything, even if we panic
return Ok(FormatInfo{ .size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false }); return Ok(FormatInfo{.size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false});
} }
virtual HorizonResult createDirectory(const FSPath& path) { virtual HorizonResult createDirectory(const FSPath& path) {
Helpers::panic("Unimplemented CreateDirectory for %s archive", name().c_str()); Helpers::panic("Unimplemented CreateDirectory for %s archive", name().c_str());
return Result::FS::AlreadyExists; return Result::FS::AlreadyExists;
} }
// Returns nullopt if opening the file failed, otherwise returns a file descriptor to it (nullptr if none is needed) // Returns nullopt if opening the file failed, otherwise returns a file descriptor to it (nullptr if none is needed)
virtual FileDescriptor openFile(const FSPath& path, const FilePerms& perms) = 0; virtual FileDescriptor openFile(const FSPath& path, const FilePerms& perms) = 0;
virtual Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) = 0; virtual Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) = 0;
virtual Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) { virtual Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) {
Helpers::panic("Unimplemented OpenDirectory for %s archive", name().c_str()); Helpers::panic("Unimplemented OpenDirectory for %s archive", name().c_str());
return Err(Result::FS::FileNotFoundAlt); return Err(Result::FS::FileNotFoundAlt);
} }
virtual void format(const FSPath& path, const FormatInfo& info) { virtual void format(const FSPath& path, const FormatInfo& info) { Helpers::panic("Unimplemented Format for %s archive", name().c_str()); }
Helpers::panic("Unimplemented Format for %s archive", name().c_str());
}
virtual HorizonResult renameFile(const FSPath& oldPath, const FSPath& newPath) { virtual HorizonResult renameFile(const FSPath& oldPath, const FSPath& newPath) {
Helpers::panic("Unimplemented RenameFile for %s archive", name().c_str()); Helpers::panic("Unimplemented RenameFile for %s archive", name().c_str());
return Result::Success; return Result::Success;
} }
// Read size bytes from a file starting at offset "offset" into a certain buffer in memory // Read size bytes from a file starting at offset "offset" into a certain buffer in memory
// Returns the number of bytes read, or nullopt if the read failed // Returns the number of bytes read, or nullopt if the read failed
virtual std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0; virtual std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0;
ArchiveBase(Memory& mem) : mem(mem) {} ArchiveBase(Memory& mem) : mem(mem) {}
}; };
struct ArchiveResource { struct ArchiveResource {

View file

@ -0,0 +1,30 @@
#pragma once
#include "archive_base.hpp"
#include "result/result.hpp"
using Result::HorizonResult;
class TWLPhotoArchive : public ArchiveBase {
public:
TWLPhotoArchive(Memory& mem) : ArchiveBase(mem) {}
std::string name() override { return "TWL_PHOTO"; }
u64 getFreeBytes() override {
Helpers::warn("Unimplemented GetFreeBytes for TWLPhoto archive");
return 32_MB;
}
HorizonResult createDirectory(const FSPath& path) override;
HorizonResult createFile(const FSPath& path, u64 size) override;
HorizonResult deleteFile(const FSPath& path) override;
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override {
Helpers::panic("Unimplemented ReadFile for TWL_PHOTO archive");
return {};
};
};

View file

@ -0,0 +1,30 @@
#pragma once
#include "archive_base.hpp"
#include "result/result.hpp"
using Result::HorizonResult;
class TWLSoundArchive : public ArchiveBase {
public:
TWLSoundArchive(Memory& mem) : ArchiveBase(mem) {}
std::string name() override { return "TWL_SOUND"; }
u64 getFreeBytes() override {
Helpers::warn("Unimplemented GetFreeBytes for TWLSound archive");
return 32_MB;
}
HorizonResult createDirectory(const FSPath& path) override;
HorizonResult createFile(const FSPath& path, u64 size) override;
HorizonResult deleteFile(const FSPath& path) override;
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override {
Helpers::panic("Unimplemented ReadFile for TWL_SOUND archive");
return {};
};
};

View file

@ -6,6 +6,8 @@
#include "fs/archive_sdmc.hpp" #include "fs/archive_sdmc.hpp"
#include "fs/archive_self_ncch.hpp" #include "fs/archive_self_ncch.hpp"
#include "fs/archive_system_save_data.hpp" #include "fs/archive_system_save_data.hpp"
#include "fs/archive_twl_photo.hpp"
#include "fs/archive_twl_sound.hpp"
#include "fs/archive_user_save_data.hpp" #include "fs/archive_user_save_data.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "kernel_types.hpp" #include "kernel_types.hpp"
@ -39,6 +41,9 @@ class FSService {
ExtSaveDataArchive sharedExtSaveData_nand; ExtSaveDataArchive sharedExtSaveData_nand;
SystemSaveDataArchive systemSaveData; SystemSaveDataArchive systemSaveData;
TWLPhotoArchive twlPhoto;
TWLSoundArchive twlSound;
ArchiveBase* getArchiveFromID(u32 id, const FSPath& archivePath); ArchiveBase* getArchiveFromID(u32 id, const FSPath& archivePath);
Rust::Result<Handle, HorizonResult> openArchiveHandle(u32 archiveID, const FSPath& path); Rust::Result<Handle, HorizonResult> openArchiveHandle(u32 archiveID, const FSPath& path);
Rust::Result<Handle, HorizonResult> openDirectoryHandle(ArchiveBase* archive, const FSPath& path); Rust::Result<Handle, HorizonResult> openDirectoryHandle(ArchiveBase* archive, const FSPath& path);
@ -87,7 +92,7 @@ class FSService {
FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config) FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config)
: mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem), : mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem),
sdmcWriteOnly(mem, true), selfNcch(mem), ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), sdmcWriteOnly(mem, true), selfNcch(mem), ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1),
userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config), systemSaveData(mem) {} userSaveData2(mem, ArchiveID::UserSaveData2), systemSaveData(mem), twlPhoto(mem), twlSound(mem), kernel(kernel), config(config) {}
void reset(); void reset();
void handleSyncRequest(u32 messagePointer); void handleSyncRequest(u32 messagePointer);

View file

@ -87,7 +87,7 @@ HorizonResult SystemSaveDataArchive::createFile(const FSPath& path, u64 size) {
HorizonResult SystemSaveDataArchive::createDirectory(const FSPath& path) { HorizonResult SystemSaveDataArchive::createDirectory(const FSPath& path) {
if (path.type == PathType::UTF16) { if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path)) { if (!isPathSafe<PathType::UTF16>(path)) {
Helpers::panic("Unsafe path in SystemSaveData::OpenFile"); Helpers::panic("Unsafe path in SystemSaveData::CreateDirectory");
} }
fs::path p = IOFile::getAppData() / ".." / "SharedFiles" / "SystemSaveData"; fs::path p = IOFile::getAppData() / ".." / "SharedFiles" / "SystemSaveData";

View file

@ -0,0 +1,40 @@
#include <algorithm>
#include <memory>
#include "fs/archive_twl_photo.hpp"
namespace fs = std::filesystem;
HorizonResult TWLPhotoArchive::createFile(const FSPath& path, u64 size) {
Helpers::panic("[TWL_PHOTO] CreateFile not yet supported");
return Result::Success;
}
HorizonResult TWLPhotoArchive::deleteFile(const FSPath& path) {
Helpers::panic("[TWL_PHOTO] Unimplemented DeleteFile");
return Result::Success;
}
HorizonResult TWLPhotoArchive::createDirectory(const FSPath& path) {
Helpers::panic("[TWL_PHOTO] CreateDirectory not yet supported");
return Result::Success;
}
FileDescriptor TWLPhotoArchive::openFile(const FSPath& path, const FilePerms& perms) {
Helpers::panic("[TWL_PHOTO] OpenFile not yet supported");
return FileError;
}
Rust::Result<ArchiveBase*, HorizonResult> TWLPhotoArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Empty) {
Helpers::panic("Unimplemented path type for TWLPhotoArchive::OpenArchive");
}
Helpers::warn("Unimplemented: TWL_PHOTO archive");
return Err(Result::FailurePlaceholder);
}
Rust::Result<DirectorySession, HorizonResult> TWLPhotoArchive::openDirectory(const FSPath& path) {
Helpers::panic("[TWL_PHOTO] OpenDirectory not yet supported");
return Err(Result::FailurePlaceholder);
}

View file

@ -0,0 +1,40 @@
#include <algorithm>
#include <memory>
#include "fs/archive_twl_sound.hpp"
namespace fs = std::filesystem;
HorizonResult TWLSoundArchive::createFile(const FSPath& path, u64 size) {
Helpers::panic("[TWL_SOUND] CreateFile not yet supported");
return Result::Success;
}
HorizonResult TWLSoundArchive::deleteFile(const FSPath& path) {
Helpers::panic("[TWL_SOUND] Unimplemented DeleteFile");
return Result::Success;
}
HorizonResult TWLSoundArchive::createDirectory(const FSPath& path) {
Helpers::panic("[TWL_SOUND] CreateDirectory not yet supported");
return Result::Success;
}
FileDescriptor TWLSoundArchive::openFile(const FSPath& path, const FilePerms& perms) {
Helpers::panic("[TWL_SOUND] OpenFile not yet supported");
return FileError;
}
Rust::Result<ArchiveBase*, HorizonResult> TWLSoundArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Empty) {
Helpers::panic("Unimplemented path type for TWLSoundArchive::OpenArchive");
}
Helpers::warn("Unimplemented: TWL_SOUND archive");
return Err(Result::FailurePlaceholder);
}
Rust::Result<DirectorySession, HorizonResult> TWLSoundArchive::openDirectory(const FSPath& path) {
Helpers::panic("[TWL_SOUND] OpenDirectory not yet supported");
return Err(Result::FailurePlaceholder);
}

View file

@ -99,6 +99,10 @@ ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
case ArchiveID::SDMC: return &sdmc; case ArchiveID::SDMC: return &sdmc;
case ArchiveID::SDMCWriteOnly: return &sdmcWriteOnly; case ArchiveID::SDMCWriteOnly: return &sdmcWriteOnly;
case ArchiveID::SavedataAndNcch: return &ncch; // This can only access NCCH outside of FSPXI case ArchiveID::SavedataAndNcch: return &ncch; // This can only access NCCH outside of FSPXI
case ArchiveID::TwlPhoto: return &twlPhoto;
case ArchiveID::TwlSound: return &twlSound;
default: default:
Helpers::panic("Unknown archive. ID: %d\n", id); Helpers::panic("Unknown archive. ID: %d\n", id);
return nullptr; return nullptr;