Compare commits
148 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26d74fefdc | ||
|
|
85d02d07fc | ||
|
|
5a9ba8342f | ||
|
|
316b933a31 | ||
|
|
21a2512cfb | ||
|
|
4f41ffdd41 | ||
|
|
7c7b2b8285 | ||
|
|
b89fb430c7 | ||
|
|
33ac8e9a85 | ||
|
|
eca5df2573 | ||
|
|
b30c5370b1 | ||
|
|
020d005d8c | ||
|
|
706892de7d | ||
|
|
3d68f6ba6c | ||
|
|
7230ceb584 | ||
|
|
afb26b190f | ||
|
|
39840bd511 | ||
|
|
e2037821b6 | ||
|
|
7b31bed043 | ||
|
|
12a6996262 | ||
|
|
379a935016 | ||
|
|
d909d700ce | ||
|
|
198805b0fa | ||
|
|
04524e76c2 | ||
|
|
3e966be6fc | ||
|
|
05a019d4ee | ||
|
|
4b22a64d63 | ||
|
|
e652110fda | ||
|
|
9cb1f759ca | ||
|
|
4a657f5fb6 | ||
|
|
20a3fbcc5a | ||
|
|
490893f119 | ||
|
|
430903a1ba | ||
|
|
f7e6e204da | ||
|
|
1ffd10ce18 | ||
|
|
c2ad530279 | ||
|
|
625558e7b0 | ||
|
|
fb8e336e95 | ||
|
|
68e5f369c1 | ||
|
|
2f465d7572 | ||
|
|
c3ec14616f | ||
|
|
7e7f94f559 | ||
|
|
6bd605cb61 | ||
|
|
b412594127 | ||
|
|
7f342ce736 | ||
|
|
b5e29babc5 | ||
|
|
008a2d35d8 | ||
|
|
f8f0913e2e | ||
|
|
fdf73c177b | ||
|
|
7212fd5227 | ||
|
|
341517a5f2 | ||
|
|
31bc72dd5b | ||
|
|
bee01b8e6c | ||
|
|
813fab2046 | ||
|
|
2b091f642f | ||
|
|
0944bfe3cb | ||
|
|
aec90ca506 | ||
|
|
ef2c955db5 | ||
|
|
dacc89b38b | ||
|
|
51a3e93f8e | ||
|
|
0d51cfe2f5 | ||
|
|
e39769b78a | ||
|
|
eb6cbfdbd8 | ||
|
|
1b3dd30ba8 | ||
|
|
854f474f52 | ||
|
|
20f6ac28f9 | ||
|
|
639346bcfb | ||
|
|
d990f2355b | ||
|
|
f89b47fdf7 | ||
|
|
913896cbd9 | ||
|
|
75767354c6 | ||
|
|
3417f46dd5 | ||
|
|
4633dd9505 | ||
|
|
f2c2383c8b | ||
|
|
b8384c0c91 | ||
|
|
9df698fa9c | ||
|
|
70a6691e3b | ||
|
|
35a3eb3467 | ||
|
|
cdf1682f79 | ||
|
|
7246b03e45 | ||
|
|
1ba63bd4d7 | ||
|
|
36460b5d58 | ||
|
|
732b8305de | ||
|
|
22c008ea20 | ||
|
|
b0c92b80b1 | ||
|
|
c30cf74ef8 | ||
|
|
3274826d28 | ||
|
|
f69c7d738b | ||
|
|
c3e39280e6 | ||
|
|
f18b555c4d | ||
|
|
9e73c92f3e | ||
|
|
ab7567e048 | ||
|
|
1cb53177cc | ||
|
|
38acb2a835 | ||
|
|
aa020b7a9b | ||
|
|
90539362bc | ||
|
|
99acda51d2 | ||
|
|
e7b518e8dd | ||
|
|
917db5cafe | ||
|
|
1f73848198 | ||
|
|
82058c7275 | ||
|
|
1a18c59964 | ||
|
|
f2d69f0dd9 | ||
|
|
d4ec24bdbb | ||
|
|
f684b02719 | ||
|
|
5c5950bd9e | ||
|
|
538418e4b0 | ||
|
|
23dc36ed71 | ||
|
|
72fd238ff2 | ||
|
|
74e3b223b7 | ||
|
|
5d0afdeae6 | ||
|
|
b0c05f5e86 | ||
|
|
7f6bf91050 | ||
|
|
ffa6a3dd5c | ||
|
|
5f57a70a7d | ||
|
|
a8026d328f | ||
|
|
b0067bb3e5 | ||
|
|
a35755dab8 | ||
|
|
c5445c764d | ||
|
|
c40e897bc0 | ||
|
|
01c168a460 | ||
|
|
098fb7438e | ||
|
|
1d1646e53f | ||
|
|
4ab40a08ff | ||
|
|
6b37634175 | ||
|
|
e0ceb4ed70 | ||
|
|
806de51c6a | ||
|
|
f21550c836 | ||
|
|
2adb30a137 | ||
|
|
1c002e1acc | ||
|
|
d0d834ad2d | ||
|
|
56a5d60276 | ||
|
|
4346150b6f | ||
|
|
586c14670e | ||
|
|
5ce0d95f1c | ||
|
|
f76bceb714 | ||
|
|
1615d56d0f | ||
|
|
2f61f8ff13 | ||
|
|
cbadda89c1 | ||
|
|
ae86329474 | ||
|
|
b0d52753c1 | ||
|
|
8171771fa8 | ||
|
|
f3ea3523b2 | ||
|
|
84963f2f20 | ||
|
|
33a4892279 | ||
|
|
f417fbcd47 | ||
|
|
62fd45d040 | ||
|
|
a78a5003fa |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ src/common/scm_rev.cpp
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
||||
CMakeLists.txt.user
|
||||
|
||||
# *nix related
|
||||
# Common convention for backup or temporary files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Reporting Issues
|
||||
|
||||
**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/yuzu-emu/yuzu/wiki/FAQ) and then either visit our Discord server, [our forum](https://community.citra-emu.org) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
|
||||
**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/yuzu-emu/yuzu/wiki/FAQ) and then either visit our [Discord server](https://discordapp.com/invite/u77vRWY), [our forum](https://community.citra-emu.org) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
|
||||
|
||||
If you believe you have a valid issue report, please post text or a screenshot from the log (the console window that opens alongside yuzu) and build version (hex string visible in the titlebar and zip filename), as well as your hardware and software information if applicable.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2016 Citra Emulator Project
|
||||
# Copyright 2018 Yuzu Emulator Project
|
||||
# Licensed under GPLv2 or any later version
|
||||
# Refer to the license.txt file included.
|
||||
|
||||
@@ -22,7 +22,7 @@ function(windows_copy_files TARGET SOURCE_DIR DEST_DIR)
|
||||
# cmake adds an extra check for command success which doesn't work too well with robocopy
|
||||
# so trick it into thinking the command was successful with the || cmd /c "exit /b 0"
|
||||
add_custom_command(TARGET ${TARGET} POST_BUILD
|
||||
COMMAND if not exist ${DEST_DIR} mkdir ${DEST_DIR} 2> nul
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEST_DIR}
|
||||
COMMAND robocopy ${SOURCE_DIR} ${DEST_DIR} ${ARGN} /NJH /NJS /NDL /NFL /NC /NS /NP || cmd /c "exit /b 0"
|
||||
)
|
||||
endfunction()
|
||||
endfunction()
|
||||
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: f7d11baa1c...990a569b7a
@@ -4,7 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(ARCHITECTURE_x86_64) && !defined(_M_ARM)
|
||||
#if !defined(ARCHITECTURE_x86_64) && !defined(ARCHITECTURE_ARM)
|
||||
#include <cstdlib> // for exit
|
||||
#endif
|
||||
#include "common/common_types.h"
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#define Crash() __asm__ __volatile__("int $3")
|
||||
#elif defined(_M_ARM)
|
||||
#elif defined(ARCHITECTURE_ARM)
|
||||
#define Crash() __asm__ __volatile__("trap")
|
||||
#else
|
||||
#define Crash() exit(1)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <sstream>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_paths.h"
|
||||
@@ -387,7 +386,7 @@ u64 GetSize(FILE* f) {
|
||||
bool CreateEmptyFile(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "{}", filename);
|
||||
|
||||
if (!FileUtil::IOFile(filename, "wb").IsOpen()) {
|
||||
if (!FileUtil::IOFile(filename, "wb")) {
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
@@ -751,7 +750,7 @@ size_t WriteStringToFile(bool text_file, const std::string& str, const char* fil
|
||||
size_t ReadFileToString(bool text_file, const char* filename, std::string& str) {
|
||||
IOFile file(filename, text_file ? "r" : "rb");
|
||||
|
||||
if (!file.IsOpen())
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
str.resize(static_cast<u32>(file.GetSize()));
|
||||
@@ -800,57 +799,6 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitPathComponents(const std::string& filename) {
|
||||
auto copy(filename);
|
||||
std::replace(copy.begin(), copy.end(), '\\', '/');
|
||||
std::vector<std::string> out;
|
||||
|
||||
std::stringstream stream(filename);
|
||||
std::string item;
|
||||
while (std::getline(stream, item, '/'))
|
||||
out.push_back(std::move(item));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string GetParentPath(const std::string& path) {
|
||||
auto out = path;
|
||||
const auto name_bck_index = out.find_last_of('\\');
|
||||
const auto name_fwd_index = out.find_last_of('/');
|
||||
size_t name_index;
|
||||
if (name_bck_index == std::string::npos || name_fwd_index == std::string::npos)
|
||||
name_index = std::min<size_t>(name_bck_index, name_fwd_index);
|
||||
else
|
||||
name_index = std::max<size_t>(name_bck_index, name_fwd_index);
|
||||
|
||||
return out.erase(name_index);
|
||||
}
|
||||
|
||||
std::string GetFilename(std::string path) {
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
auto name_index = path.find_last_of('/');
|
||||
if (name_index == std::string::npos)
|
||||
return "";
|
||||
return path.substr(name_index + 1);
|
||||
}
|
||||
|
||||
std::string GetExtensionFromFilename(const std::string& name) {
|
||||
size_t index = name.find_last_of('.');
|
||||
if (index == std::string::npos)
|
||||
return "";
|
||||
|
||||
return name.substr(index + 1);
|
||||
}
|
||||
|
||||
std::string RemoveTrailingSlash(const std::string& path) {
|
||||
if (path.empty())
|
||||
return path;
|
||||
if (path.back() == '\\' || path.back() == '/')
|
||||
return path.substr(0, path.size() - 1);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
IOFile::IOFile() {}
|
||||
|
||||
IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
|
||||
@@ -872,6 +820,7 @@ IOFile& IOFile::operator=(IOFile&& other) noexcept {
|
||||
|
||||
void IOFile::Swap(IOFile& other) noexcept {
|
||||
std::swap(m_file, other.m_file);
|
||||
std::swap(m_good, other.m_good);
|
||||
}
|
||||
|
||||
bool IOFile::Open(const std::string& filename, const char openmode[], int flags) {
|
||||
@@ -888,15 +837,16 @@ bool IOFile::Open(const std::string& filename, const char openmode[], int flags)
|
||||
m_file = fopen(filename.c_str(), openmode);
|
||||
#endif
|
||||
|
||||
return IsOpen();
|
||||
m_good = IsOpen();
|
||||
return m_good;
|
||||
}
|
||||
|
||||
bool IOFile::Close() {
|
||||
if (!IsOpen() || 0 != std::fclose(m_file))
|
||||
return false;
|
||||
m_good = false;
|
||||
|
||||
m_file = nullptr;
|
||||
return true;
|
||||
return m_good;
|
||||
}
|
||||
|
||||
u64 IOFile::GetSize() const {
|
||||
@@ -906,8 +856,11 @@ u64 IOFile::GetSize() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IOFile::Seek(s64 off, int origin) const {
|
||||
return IsOpen() && 0 == fseeko(m_file, off, origin);
|
||||
bool IOFile::Seek(s64 off, int origin) {
|
||||
if (!IsOpen() || 0 != fseeko(m_file, off, origin))
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
|
||||
u64 IOFile::Tell() const {
|
||||
@@ -918,20 +871,26 @@ u64 IOFile::Tell() const {
|
||||
}
|
||||
|
||||
bool IOFile::Flush() {
|
||||
return IsOpen() && 0 == std::fflush(m_file);
|
||||
if (!IsOpen() || 0 != std::fflush(m_file))
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
|
||||
bool IOFile::Resize(u64 size) {
|
||||
return IsOpen() && 0 ==
|
||||
if (!IsOpen() || 0 !=
|
||||
#ifdef _WIN32
|
||||
// ector: _chsize sucks, not 64-bit safe
|
||||
// F|RES: changed to _chsize_s. i think it is 64-bit safe
|
||||
_chsize_s(_fileno(m_file), size)
|
||||
// ector: _chsize sucks, not 64-bit safe
|
||||
// F|RES: changed to _chsize_s. i think it is 64-bit safe
|
||||
_chsize_s(_fileno(m_file), size)
|
||||
#else
|
||||
// TODO: handle 64bit and growing
|
||||
ftruncate(fileno(m_file), size)
|
||||
// TODO: handle 64bit and growing
|
||||
ftruncate(fileno(m_file), size)
|
||||
#endif
|
||||
;
|
||||
)
|
||||
m_good = false;
|
||||
|
||||
return m_good;
|
||||
}
|
||||
|
||||
} // namespace FileUtil
|
||||
|
||||
@@ -150,31 +150,6 @@ size_t ReadFileToString(bool text_file, const char* filename, std::string& str);
|
||||
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
|
||||
std::array<char, 4>& extension);
|
||||
|
||||
// Splits the path on '/' or '\' and put the components into a vector
|
||||
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||
std::vector<std::string> SplitPathComponents(const std::string& filename);
|
||||
|
||||
// Gets all of the text prior to the last '/' or '\' in the path.
|
||||
std::string GetParentPath(const std::string& path);
|
||||
|
||||
// Gets the filename of the path
|
||||
std::string GetFilename(std::string path);
|
||||
|
||||
// Gets the extension of the filename
|
||||
std::string GetExtensionFromFilename(const std::string& name);
|
||||
|
||||
// Removes the final '/' or '\' if one exists
|
||||
std::string RemoveTrailingSlash(const std::string& path);
|
||||
|
||||
// Creates a new vector containing indices [first, last) from the original.
|
||||
template <typename T>
|
||||
std::vector<T> SliceVector(const std::vector<T>& vector, size_t first, size_t last) {
|
||||
if (first >= last)
|
||||
return {};
|
||||
last = std::min<size_t>(last, vector.size());
|
||||
return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
|
||||
}
|
||||
|
||||
// simple wrapper for cstdlib file functions to
|
||||
// hopefully will make error checking easier
|
||||
// and make forgetting an fclose() harder
|
||||
@@ -197,27 +172,41 @@ public:
|
||||
bool Close();
|
||||
|
||||
template <typename T>
|
||||
size_t ReadArray(T* data, size_t length) const {
|
||||
size_t ReadArray(T* data, size_t length) {
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
|
||||
if (!IsOpen())
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return std::fread(data, sizeof(T), length, m_file);
|
||||
size_t items_read = std::fread(data, sizeof(T), length, m_file);
|
||||
if (items_read != length)
|
||||
m_good = false;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t WriteArray(const T* data, size_t length) {
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
if (!IsOpen())
|
||||
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
return -1;
|
||||
return std::fwrite(data, sizeof(T), length, m_file);
|
||||
}
|
||||
|
||||
size_t items_written = std::fwrite(data, sizeof(T), length, m_file);
|
||||
if (items_written != length)
|
||||
m_good = false;
|
||||
|
||||
return items_written;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t ReadBytes(T* data, size_t length) const {
|
||||
size_t ReadBytes(T* data, size_t length) {
|
||||
static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
|
||||
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||
}
|
||||
@@ -242,7 +231,15 @@ public:
|
||||
return nullptr != m_file;
|
||||
}
|
||||
|
||||
bool Seek(s64 off, int origin) const;
|
||||
// m_good is set to false when a read, write or other function fails
|
||||
bool IsGood() const {
|
||||
return m_good;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return IsGood();
|
||||
}
|
||||
|
||||
bool Seek(s64 off, int origin);
|
||||
u64 Tell() const;
|
||||
u64 GetSize() const;
|
||||
bool Resize(u64 size);
|
||||
@@ -250,11 +247,13 @@ public:
|
||||
|
||||
// clear error state
|
||||
void Clear() {
|
||||
m_good = true;
|
||||
std::clearerr(m_file);
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* m_file = nullptr;
|
||||
bool m_good = true;
|
||||
};
|
||||
|
||||
} // namespace FileUtil
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
#include <unistd.h>
|
||||
#define PAGE_MASK (getpagesize() - 1)
|
||||
#define round_page(x) ((((unsigned long)(x)) + PAGE_MASK) & ~(PAGE_MASK))
|
||||
@@ -30,7 +30,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
#else
|
||||
static char* map_hint = nullptr;
|
||||
#if defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
// This OS has no flag to enforce allocation below the 4 GB boundary,
|
||||
// but if we hint that we want a low address it is very likely we will
|
||||
// get one.
|
||||
@@ -42,7 +42,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
#endif
|
||||
void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_ANON | MAP_PRIVATE
|
||||
#if defined(ARCHITECTURE_X64) && defined(MAP_32BIT)
|
||||
#if defined(ARCHITECTURE_x86_64) && defined(MAP_32BIT)
|
||||
| (low ? MAP_32BIT : 0)
|
||||
#endif
|
||||
,
|
||||
@@ -57,7 +57,7 @@ void* AllocateExecutableMemory(size_t size, bool low) {
|
||||
#endif
|
||||
LOG_ERROR(Common_Memory, "Failed to allocate executable memory");
|
||||
}
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_X64) && !defined(MAP_32BIT)
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
else {
|
||||
if (low) {
|
||||
map_hint += size;
|
||||
|
||||
@@ -134,7 +134,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
|
||||
size_t dir_end = full_path.find_last_of("/"
|
||||
// windows needs the : included for something like just "C:" to be considered a directory
|
||||
#ifdef _WIN32
|
||||
":"
|
||||
"\\:"
|
||||
#endif
|
||||
);
|
||||
if (std::string::npos == dir_end)
|
||||
|
||||
@@ -69,7 +69,7 @@ inline u32 swap32(u32 _data) {
|
||||
inline u64 swap64(u64 _data) {
|
||||
return _byteswap_uint64(_data);
|
||||
}
|
||||
#elif _M_ARM
|
||||
#elif ARCHITECTURE_ARM
|
||||
inline u16 swap16(u16 _data) {
|
||||
u32 data = _data;
|
||||
__asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data));
|
||||
|
||||
@@ -8,9 +8,9 @@ add_library(core STATIC
|
||||
core_cpu.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/directory.h
|
||||
file_sys/disk_filesystem.cpp
|
||||
file_sys/disk_filesystem.h
|
||||
file_sys/errors.h
|
||||
file_sys/filesystem.cpp
|
||||
file_sys/filesystem.h
|
||||
@@ -20,13 +20,15 @@ add_library(core STATIC
|
||||
file_sys/path_parser.h
|
||||
file_sys/program_metadata.cpp
|
||||
file_sys/program_metadata.h
|
||||
file_sys/romfs_factory.cpp
|
||||
file_sys/romfs_factory.h
|
||||
file_sys/romfs_filesystem.cpp
|
||||
file_sys/romfs_filesystem.h
|
||||
file_sys/savedata_factory.cpp
|
||||
file_sys/savedata_factory.h
|
||||
file_sys/sdmc_factory.cpp
|
||||
file_sys/sdmc_factory.h
|
||||
file_sys/storage.h
|
||||
file_sys/vfs.cpp
|
||||
file_sys/vfs.h
|
||||
file_sys/vfs_offset.cpp
|
||||
file_sys/vfs_offset.h
|
||||
file_sys/vfs_real.cpp
|
||||
file_sys/vfs_real.h
|
||||
frontend/emu_window.cpp
|
||||
frontend/emu_window.h
|
||||
frontend/framebuffer_layout.cpp
|
||||
|
||||
@@ -193,11 +193,11 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
}
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
SaveContext(thread->context);
|
||||
if (last_bkpt_hit) {
|
||||
if (last_bkpt_hit || (num_instructions == 1)) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
}
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory_setup.h"
|
||||
#include "core/settings.h"
|
||||
#include "file_sys/vfs_real.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -85,7 +84,7 @@ System::ResultStatus System::SingleStep() {
|
||||
}
|
||||
|
||||
System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(filepath));
|
||||
app_loader = Loader::GetLoader(filepath);
|
||||
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
|
||||
constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
|
||||
|
||||
constexpr u64 SECTION_HEADER_SIZE = 0x200;
|
||||
constexpr u64 SECTION_HEADER_OFFSET = 0x400;
|
||||
|
||||
constexpr u32 IVFC_MAX_LEVEL = 6;
|
||||
|
||||
enum class NCASectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 };
|
||||
|
||||
struct NCASectionHeaderBlock {
|
||||
INSERT_PADDING_BYTES(3);
|
||||
NCASectionFilesystemType filesystem_type;
|
||||
u8 crypto_type;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
|
||||
|
||||
struct PFS0Superblock {
|
||||
NCASectionHeaderBlock header_block;
|
||||
std::array<u8, 0x20> hash;
|
||||
u32_le size;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u64_le hash_table_offset;
|
||||
u64_le hash_table_size;
|
||||
u64_le pfs0_header_offset;
|
||||
u64_le pfs0_size;
|
||||
INSERT_PADDING_BYTES(432);
|
||||
};
|
||||
static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
|
||||
|
||||
struct IVFCLevel {
|
||||
u64_le offset;
|
||||
u64_le size;
|
||||
u32_le block_size;
|
||||
u32_le reserved;
|
||||
};
|
||||
static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
|
||||
|
||||
struct RomFSSuperblock {
|
||||
NCASectionHeaderBlock header_block;
|
||||
u32_le magic;
|
||||
u32_le magic_number;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
std::array<IVFCLevel, 6> levels;
|
||||
INSERT_PADDING_BYTES(64);
|
||||
};
|
||||
static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size.");
|
||||
|
||||
NCA::NCA(VirtualFile file_) : file(file_) {
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
if (!IsValidNCA(header)) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
return;
|
||||
}
|
||||
|
||||
std::ptrdiff_t number_sections =
|
||||
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
|
||||
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
|
||||
// Seek to beginning of this section.
|
||||
NCASectionHeaderBlock block{};
|
||||
if (sizeof(NCASectionHeaderBlock) !=
|
||||
file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
if (block.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
RomFSSuperblock sb{};
|
||||
if (sizeof(RomFSSuperblock) !=
|
||||
file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
const size_t romfs_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
|
||||
sb.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const size_t romfs_size = sb.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset));
|
||||
romfs = files.back();
|
||||
} else if (block.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
PFS0Superblock sb{};
|
||||
// Seek back to beginning of this section.
|
||||
if (sizeof(PFS0Superblock) !=
|
||||
file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
|
||||
MEDIA_OFFSET_MULTIPLIER) +
|
||||
sb.pfs0_header_offset;
|
||||
u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(
|
||||
std::make_shared<OffsetVfsFile>(file, size, offset));
|
||||
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.emplace_back(npfs);
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NCA::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return {};
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return {};
|
||||
return dirs;
|
||||
}
|
||||
|
||||
std::string NCA::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
NCAContentType NCA::GetType() const {
|
||||
return header.content_type;
|
||||
}
|
||||
|
||||
u64 NCA::GetTitleId() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return {};
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
VirtualFile NCA::GetRomFS() const {
|
||||
return romfs;
|
||||
}
|
||||
|
||||
VirtualDir NCA::GetExeFS() const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class NCAContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 };
|
||||
|
||||
struct NCASectionTableEntry {
|
||||
u32_le media_offset;
|
||||
u32_le media_end_offset;
|
||||
INSERT_PADDING_BYTES(0x8);
|
||||
};
|
||||
static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
|
||||
|
||||
struct NCAHeader {
|
||||
std::array<u8, 0x100> rsa_signature_1;
|
||||
std::array<u8, 0x100> rsa_signature_2;
|
||||
u32_le magic;
|
||||
u8 is_system;
|
||||
NCAContentType content_type;
|
||||
u8 crypto_type;
|
||||
u8 key_index;
|
||||
u64_le size;
|
||||
u64_le title_id;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
u32_le sdk_version;
|
||||
u8 crypto_type_2;
|
||||
INSERT_PADDING_BYTES(15);
|
||||
std::array<u8, 0x10> rights_id;
|
||||
std::array<NCASectionTableEntry, 0x4> section_tables;
|
||||
std::array<std::array<u8, 0x20>, 0x4> hash_tables;
|
||||
std::array<std::array<u8, 0x10>, 0x4> key_area;
|
||||
INSERT_PADDING_BYTES(0xC0);
|
||||
};
|
||||
static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
|
||||
|
||||
static bool IsDirectoryExeFS(std::shared_ptr<FileSys::VfsDirectory> pfs) {
|
||||
// According to switchbrew, an exefs must only contain these two files:
|
||||
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
|
||||
}
|
||||
|
||||
static bool IsValidNCA(const NCAHeader& header) {
|
||||
return header.magic == Common::MakeMagic('N', 'C', 'A', '2') ||
|
||||
header.magic == Common::MakeMagic('N', 'C', 'A', '3');
|
||||
}
|
||||
|
||||
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
class NCA : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NCA(VirtualFile file);
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::string GetName() const override;
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
|
||||
NCAContentType GetType() const;
|
||||
u64 GetTitleId() const;
|
||||
|
||||
VirtualFile GetRomFS() const;
|
||||
VirtualDir GetExeFS() const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::vector<VirtualFile> files;
|
||||
|
||||
VirtualFile romfs = nullptr;
|
||||
VirtualDir exefs = nullptr;
|
||||
VirtualFile file;
|
||||
|
||||
NCAHeader header{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
237
src/core/file_sys/disk_filesystem.cpp
Normal file
237
src/core/file_sys/disk_filesystem.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/disk_filesystem.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
static std::string ModeFlagsToString(Mode mode) {
|
||||
std::string mode_str;
|
||||
u32 mode_flags = static_cast<u32>(mode);
|
||||
|
||||
// Calculate the correct open mode for the file.
|
||||
if ((mode_flags & static_cast<u32>(Mode::Read)) &&
|
||||
(mode_flags & static_cast<u32>(Mode::Write))) {
|
||||
if (mode_flags & static_cast<u32>(Mode::Append))
|
||||
mode_str = "a+";
|
||||
else
|
||||
mode_str = "r+";
|
||||
} else {
|
||||
if (mode_flags & static_cast<u32>(Mode::Read))
|
||||
mode_str = "r";
|
||||
else if (mode_flags & static_cast<u32>(Mode::Append))
|
||||
mode_str = "a";
|
||||
else if (mode_flags & static_cast<u32>(Mode::Write))
|
||||
mode_str = "w";
|
||||
}
|
||||
|
||||
mode_str += "b";
|
||||
|
||||
return mode_str;
|
||||
}
|
||||
|
||||
std::string Disk_FileSystem::GetName() const {
|
||||
return "Disk";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path,
|
||||
Mode mode) const {
|
||||
|
||||
// Calculate the correct open mode for the file.
|
||||
std::string mode_str = ModeFlagsToString(mode);
|
||||
|
||||
std::string full_path = base_directory + path;
|
||||
auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str());
|
||||
|
||||
if (!file->IsOpen()) {
|
||||
return ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
|
||||
return MakeResult<std::unique_ptr<StorageBackend>>(
|
||||
std::make_unique<Disk_Storage>(std::move(file)));
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::DeleteFile(const std::string& path) const {
|
||||
if (!FileUtil::Exists(path)) {
|
||||
return ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
|
||||
FileUtil::Delete(path);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::RenameFile(const std::string& src_path,
|
||||
const std::string& dest_path) const {
|
||||
const std::string full_src_path = base_directory + src_path;
|
||||
const std::string full_dest_path = base_directory + dest_path;
|
||||
|
||||
if (!FileUtil::Exists(full_src_path)) {
|
||||
return ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
// TODO(wwylele): Use correct error code
|
||||
return FileUtil::Rename(full_src_path, full_dest_path) ? RESULT_SUCCESS : ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
|
||||
std::string full_path = base_directory + path;
|
||||
if (size == 0) {
|
||||
FileUtil::CreateEmptyFile(full_path);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(full_path, "wb");
|
||||
// Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
|
||||
// We do this by seeking to the right size, then writing a single null byte.
|
||||
if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_FS, "Too large file");
|
||||
// TODO(Subv): Find out the correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const {
|
||||
// TODO(Subv): Perform path validation to prevent escaping the emulator sandbox.
|
||||
std::string full_path = base_directory + path;
|
||||
|
||||
if (FileUtil::CreateDir(full_path)) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", full_path);
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory(
|
||||
const std::string& path) const {
|
||||
|
||||
std::string full_path = base_directory + path;
|
||||
|
||||
if (!FileUtil::IsDirectory(full_path)) {
|
||||
// TODO(Subv): Find the correct error code for this.
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
auto directory = std::make_unique<Disk_Directory>(full_path);
|
||||
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
|
||||
}
|
||||
|
||||
u64 Disk_FileSystem::GetFreeSpaceSize() const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& path) const {
|
||||
std::string full_path = base_directory + path;
|
||||
if (!FileUtil::Exists(full_path)) {
|
||||
return ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (FileUtil::IsDirectory(full_path))
|
||||
return MakeResult(EntryType::Directory);
|
||||
|
||||
return MakeResult(EntryType::File);
|
||||
}
|
||||
|
||||
ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
|
||||
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
|
||||
file->Seek(offset, SEEK_SET);
|
||||
return MakeResult<size_t>(file->ReadBytes(buffer, length));
|
||||
}
|
||||
|
||||
ResultVal<size_t> Disk_Storage::Write(const u64 offset, const size_t length, const bool flush,
|
||||
const u8* buffer) const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
file->Seek(offset, SEEK_SET);
|
||||
size_t written = file->WriteBytes(buffer, length);
|
||||
if (flush) {
|
||||
file->Flush();
|
||||
}
|
||||
return MakeResult<size_t>(written);
|
||||
}
|
||||
|
||||
u64 Disk_Storage::GetSize() const {
|
||||
return file->GetSize();
|
||||
}
|
||||
|
||||
bool Disk_Storage::SetSize(const u64 size) const {
|
||||
file->Resize(size);
|
||||
file->Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
Disk_Directory::Disk_Directory(const std::string& path) {
|
||||
unsigned size = FileUtil::ScanDirectoryTree(path, directory);
|
||||
directory.size = size;
|
||||
directory.isDirectory = true;
|
||||
children_iterator = directory.children.begin();
|
||||
}
|
||||
|
||||
u64 Disk_Directory::Read(const u64 count, Entry* entries) {
|
||||
u64 entries_read = 0;
|
||||
|
||||
while (entries_read < count && children_iterator != directory.children.cend()) {
|
||||
const FileUtil::FSTEntry& file = *children_iterator;
|
||||
const std::string& filename = file.virtualName;
|
||||
Entry& entry = entries[entries_read];
|
||||
|
||||
LOG_TRACE(Service_FS, "File {}: size={} dir={}", filename, file.size, file.isDirectory);
|
||||
|
||||
// TODO(Link Mauve): use a proper conversion to UTF-16.
|
||||
for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
|
||||
entry.filename[j] = filename[j];
|
||||
if (!filename[j])
|
||||
break;
|
||||
}
|
||||
|
||||
if (file.isDirectory) {
|
||||
entry.file_size = 0;
|
||||
entry.type = EntryType::Directory;
|
||||
} else {
|
||||
entry.file_size = file.size;
|
||||
entry.type = EntryType::File;
|
||||
}
|
||||
|
||||
++entries_read;
|
||||
++children_iterator;
|
||||
}
|
||||
return entries_read;
|
||||
}
|
||||
|
||||
u64 Disk_Directory::GetEntryCount() const {
|
||||
// We convert the children iterator into a const_iterator to allow template argument deduction
|
||||
// in std::distance.
|
||||
std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator;
|
||||
return std::distance(current, directory.children.end());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
84
src/core/file_sys/disk_filesystem.h
Normal file
84
src/core/file_sys/disk_filesystem.h
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/storage.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class Disk_FileSystem : public FileSystemBackend {
|
||||
public:
|
||||
explicit Disk_FileSystem(std::string base_directory)
|
||||
: base_directory(std::move(base_directory)) {}
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
|
||||
Mode mode) const override;
|
||||
ResultCode DeleteFile(const std::string& path) const override;
|
||||
ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
|
||||
ResultCode DeleteDirectory(const Path& path) const override;
|
||||
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||
ResultCode CreateFile(const std::string& path, u64 size) const override;
|
||||
ResultCode CreateDirectory(const std::string& path) const override;
|
||||
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
|
||||
const std::string& path) const override;
|
||||
u64 GetFreeSpaceSize() const override;
|
||||
ResultVal<EntryType> GetEntryType(const std::string& path) const override;
|
||||
|
||||
protected:
|
||||
std::string base_directory;
|
||||
};
|
||||
|
||||
class Disk_Storage : public StorageBackend {
|
||||
public:
|
||||
explicit Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {}
|
||||
|
||||
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
|
||||
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
|
||||
u64 GetSize() const override;
|
||||
bool SetSize(u64 size) const override;
|
||||
bool Close() const override {
|
||||
return false;
|
||||
}
|
||||
void Flush() const override {}
|
||||
|
||||
private:
|
||||
std::shared_ptr<FileUtil::IOFile> file;
|
||||
};
|
||||
|
||||
class Disk_Directory : public DirectoryBackend {
|
||||
public:
|
||||
explicit Disk_Directory(const std::string& path);
|
||||
|
||||
~Disk_Directory() override {
|
||||
Close();
|
||||
}
|
||||
|
||||
u64 Read(const u64 count, Entry* entries) override;
|
||||
u64 GetEntryCount() const override;
|
||||
|
||||
bool Close() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
FileUtil::FSTEntry directory;
|
||||
|
||||
// We need to remember the last entry we returned, so a subsequent call to Read will continue
|
||||
// from the next one. This iterator will always point to the next unread entry.
|
||||
std::vector<FileUtil::FSTEntry>::iterator children_iterator;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -11,6 +11,7 @@ namespace FileSys {
|
||||
namespace ErrCodes {
|
||||
enum {
|
||||
NotFound = 1,
|
||||
SaveDataNotFound = 1002,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -66,4 +66,136 @@ private:
|
||||
std::u16string u16str;
|
||||
};
|
||||
|
||||
/// Parameters of the archive, as specified in the Create or Format call.
|
||||
struct ArchiveFormatInfo {
|
||||
u32_le total_size; ///< The pre-defined size of the archive.
|
||||
u32_le number_directories; ///< The pre-defined number of directories in the archive.
|
||||
u32_le number_files; ///< The pre-defined number of files in the archive.
|
||||
u8 duplicate_data; ///< Whether the archive should duplicate the data.
|
||||
};
|
||||
static_assert(std::is_pod<ArchiveFormatInfo>::value, "ArchiveFormatInfo is not POD");
|
||||
|
||||
class FileSystemBackend : NonCopyable {
|
||||
public:
|
||||
virtual ~FileSystemBackend() {}
|
||||
|
||||
/**
|
||||
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
|
||||
*/
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
/**
|
||||
* Create a file specified by its path
|
||||
* @param path Path relative to the Archive
|
||||
* @param size The size of the new file, filled with zeroes
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode CreateFile(const std::string& path, u64 size) const = 0;
|
||||
|
||||
/**
|
||||
* Delete a file specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode DeleteFile(const std::string& path) const = 0;
|
||||
|
||||
/**
|
||||
* Create a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode CreateDirectory(const std::string& path) const = 0;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode DeleteDirectory(const Path& path) const = 0;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path and anything under it
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0;
|
||||
|
||||
/**
|
||||
* Rename a File specified by its path
|
||||
* @param src_path Source path relative to the archive
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode RenameFile(const std::string& src_path,
|
||||
const std::string& dest_path) const = 0;
|
||||
|
||||
/**
|
||||
* Rename a Directory specified by its path
|
||||
* @param src_path Source path relative to the archive
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
|
||||
|
||||
/**
|
||||
* Open a file specified by its path, using the specified mode
|
||||
* @param path Path relative to the archive
|
||||
* @param mode Mode to open the file with
|
||||
* @return Opened file, or error code
|
||||
*/
|
||||
virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
|
||||
Mode mode) const = 0;
|
||||
|
||||
/**
|
||||
* Open a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Opened directory, or error code
|
||||
*/
|
||||
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
|
||||
const std::string& path) const = 0;
|
||||
|
||||
/**
|
||||
* Get the free space
|
||||
* @return The number of free bytes in the archive
|
||||
*/
|
||||
virtual u64 GetFreeSpaceSize() const = 0;
|
||||
|
||||
/**
|
||||
* Get the type of the specified path
|
||||
* @return The type of the specified path or error code
|
||||
*/
|
||||
virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0;
|
||||
};
|
||||
|
||||
class FileSystemFactory : NonCopyable {
|
||||
public:
|
||||
virtual ~FileSystemFactory() {}
|
||||
|
||||
/**
|
||||
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
|
||||
*/
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
/**
|
||||
* Tries to open the archive of this type with the specified path
|
||||
* @param path Path to the archive
|
||||
* @return An ArchiveBackend corresponding operating specified archive path.
|
||||
*/
|
||||
virtual ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) = 0;
|
||||
|
||||
/**
|
||||
* Deletes the archive contents and then re-creates the base folder
|
||||
* @param path Path to the archive
|
||||
* @return ResultCode of the operation, 0 on success
|
||||
*/
|
||||
virtual ResultCode Format(const Path& path) = 0;
|
||||
|
||||
/**
|
||||
* Retrieves the format info about the archive with the specified path
|
||||
* @param path Path to the archive
|
||||
* @return Format information about the archive or error code
|
||||
*/
|
||||
virtual ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const = 0;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -6,30 +6,29 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
|
||||
// At least be as large as the header
|
||||
if (file->GetSize() < sizeof(Header)) {
|
||||
status = Loader::ResultStatus::Error;
|
||||
return;
|
||||
}
|
||||
Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, size_t offset) {
|
||||
FileUtil::IOFile file(file_path, "rb");
|
||||
if (!file.IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
// At least be as large as the header
|
||||
if (file.GetSize() < sizeof(Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
file.Seek(offset, SEEK_SET);
|
||||
// For cartridges, HFSs can get very large, so we need to calculate the size up to
|
||||
// the actual content itself instead of just blindly reading in the entire file.
|
||||
Header pfs_header;
|
||||
if (sizeof(Header) != file->ReadObject(&pfs_header)) {
|
||||
status = Loader::ResultStatus::Error;
|
||||
return;
|
||||
}
|
||||
if (!file.ReadBytes(&pfs_header, sizeof(Header)))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') &&
|
||||
pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
return;
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
|
||||
bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
|
||||
@@ -39,86 +38,99 @@ PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
|
||||
sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size;
|
||||
|
||||
// Actually read in now...
|
||||
std::vector<u8> file_data = file->ReadBytes(metadata_size);
|
||||
file.Seek(offset, SEEK_SET);
|
||||
std::vector<u8> file_data(metadata_size);
|
||||
|
||||
if (file_data.size() != metadata_size) {
|
||||
status = Loader::ResultStatus::Error;
|
||||
return;
|
||||
}
|
||||
if (!file.ReadBytes(file_data.data(), metadata_size))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
size_t total_size = file_data.size();
|
||||
if (total_size < sizeof(Header)) {
|
||||
status = Loader::ResultStatus::Error;
|
||||
return;
|
||||
}
|
||||
Loader::ResultStatus result = Load(file_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
LOG_ERROR(Service_FS, "Failed to load PFS from file {}!", file_path);
|
||||
|
||||
memcpy(&pfs_header, file_data.data(), sizeof(Header));
|
||||
return result;
|
||||
}
|
||||
|
||||
Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data, size_t offset) {
|
||||
size_t total_size = file_data.size() - offset;
|
||||
if (total_size < sizeof(Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
memcpy(&pfs_header, &file_data[offset], sizeof(Header));
|
||||
if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') &&
|
||||
pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) {
|
||||
status = Loader::ResultStatus::ErrorInvalidFormat;
|
||||
return;
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
|
||||
is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
|
||||
|
||||
size_t entries_offset = sizeof(Header);
|
||||
size_t entries_offset = offset + sizeof(Header);
|
||||
size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);
|
||||
size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size);
|
||||
content_offset = strtab_offset + pfs_header.strtab_size;
|
||||
for (u16 i = 0; i < pfs_header.num_entries; i++) {
|
||||
FSEntry entry;
|
||||
FileEntry entry;
|
||||
|
||||
memcpy(&entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry));
|
||||
std::string name(
|
||||
reinterpret_cast<const char*>(&file_data[strtab_offset + entry.strtab_offset]));
|
||||
|
||||
pfs_files.emplace_back(
|
||||
std::make_shared<OffsetVfsFile>(file, entry.size, content_offset + entry.offset, name));
|
||||
memcpy(&entry.fs_entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry));
|
||||
entry.name = std::string(reinterpret_cast<const char*>(
|
||||
&file_data[strtab_offset + entry.fs_entry.strtab_offset]));
|
||||
pfs_entries.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
content_offset = strtab_offset + pfs_header.strtab_size;
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus PartitionFilesystem::GetStatus() const {
|
||||
return status;
|
||||
u32 PartitionFilesystem::GetNumEntries() const {
|
||||
return pfs_header.num_entries;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
|
||||
return pfs_files;
|
||||
u64 PartitionFilesystem::GetEntryOffset(u32 index) const {
|
||||
if (index > GetNumEntries())
|
||||
return 0;
|
||||
|
||||
return content_offset + pfs_entries[index].fs_entry.offset;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
|
||||
return {};
|
||||
u64 PartitionFilesystem::GetEntrySize(u32 index) const {
|
||||
if (index > GetNumEntries())
|
||||
return 0;
|
||||
|
||||
return pfs_entries[index].fs_entry.size;
|
||||
}
|
||||
|
||||
std::string PartitionFilesystem::GetName() const {
|
||||
return is_hfs ? "HFS0" : "PFS0";
|
||||
std::string PartitionFilesystem::GetEntryName(u32 index) const {
|
||||
if (index > GetNumEntries())
|
||||
return "";
|
||||
|
||||
return pfs_entries[index].name;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const {
|
||||
// TODO(DarkLordZach): Add support for nested containers.
|
||||
return nullptr;
|
||||
u64 PartitionFilesystem::GetFileOffset(const std::string& name) const {
|
||||
for (u32 i = 0; i < pfs_header.num_entries; i++) {
|
||||
if (pfs_entries[i].name == name)
|
||||
return content_offset + pfs_entries[i].fs_entry.offset;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PartitionFilesystem::PrintDebugInfo() const {
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic);
|
||||
u64 PartitionFilesystem::GetFileSize(const std::string& name) const {
|
||||
for (u32 i = 0; i < pfs_header.num_entries; i++) {
|
||||
if (pfs_entries[i].name == name)
|
||||
return pfs_entries[i].fs_entry.size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PartitionFilesystem::Print() const {
|
||||
LOG_DEBUG(Service_FS, "Magic: {}", pfs_header.magic);
|
||||
LOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries);
|
||||
for (u32 i = 0; i < pfs_header.num_entries; i++) {
|
||||
LOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i,
|
||||
pfs_files[i]->GetName(), pfs_files[i]->GetSize(),
|
||||
dynamic_cast<OffsetVfsFile*>(pfs_files[i].get())->GetOffset());
|
||||
pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size,
|
||||
GetFileOffset(pfs_entries[i].name));
|
||||
}
|
||||
}
|
||||
|
||||
bool PartitionFilesystem::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
auto iter = std::find(pfs_files.begin(), pfs_files.end(), file);
|
||||
if (iter == pfs_files.end())
|
||||
return false;
|
||||
|
||||
pfs_files[iter - pfs_files.begin()] = pfs_files.back();
|
||||
pfs_files.pop_back();
|
||||
|
||||
pfs_dirs.emplace_back(dir);
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
@@ -22,19 +21,19 @@ namespace FileSys {
|
||||
* Helper which implements an interface to parse PFS/HFS filesystems.
|
||||
* Data can either be loaded from a file path or data with an offset into it.
|
||||
*/
|
||||
class PartitionFilesystem : public ReadOnlyVfsDirectory {
|
||||
class PartitionFilesystem {
|
||||
public:
|
||||
explicit PartitionFilesystem(std::shared_ptr<VfsFile> file);
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
Loader::ResultStatus Load(const std::string& file_path, size_t offset = 0);
|
||||
Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0);
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::string GetName() const override;
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
void PrintDebugInfo() const;
|
||||
u32 GetNumEntries() const;
|
||||
u64 GetEntryOffset(u32 index) const;
|
||||
u64 GetEntrySize(u32 index) const;
|
||||
std::string GetEntryName(u32 index) const;
|
||||
u64 GetFileOffset(const std::string& name) const;
|
||||
u64 GetFileSize(const std::string& name) const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
void Print() const;
|
||||
|
||||
private:
|
||||
struct Header {
|
||||
@@ -73,14 +72,16 @@ private:
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
Loader::ResultStatus status;
|
||||
struct FileEntry {
|
||||
FSEntry fs_entry;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
Header pfs_header;
|
||||
bool is_hfs;
|
||||
size_t content_offset;
|
||||
|
||||
std::vector<VirtualFile> pfs_files;
|
||||
std::vector<VirtualDir> pfs_dirs;
|
||||
std::vector<FileEntry> pfs_entries;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -9,29 +9,40 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
|
||||
size_t total_size = static_cast<size_t>(file->GetSize());
|
||||
Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) {
|
||||
FileUtil::IOFile file(file_path, "rb");
|
||||
if (!file.IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
std::vector<u8> file_data(file.GetSize());
|
||||
|
||||
if (!file.ReadBytes(file_data.data(), file_data.size()))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
Loader::ResultStatus result = Load(file_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
LOG_ERROR(Service_FS, "Failed to load NPDM from file {}!", file_path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Loader::ResultStatus ProgramMetadata::Load(const std::vector<u8> file_data, size_t offset) {
|
||||
size_t total_size = static_cast<size_t>(file_data.size() - offset);
|
||||
if (total_size < sizeof(Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
// TODO(DarkLordZach): Use ReadObject when Header/AcidHeader becomes trivially copyable.
|
||||
std::vector<u8> npdm_header_data = file->ReadBytes(sizeof(Header));
|
||||
if (sizeof(Header) != npdm_header_data.size())
|
||||
return Loader::ResultStatus::Error;
|
||||
std::memcpy(&npdm_header, npdm_header_data.data(), sizeof(Header));
|
||||
size_t header_offset = offset;
|
||||
memcpy(&npdm_header, &file_data[offset], sizeof(Header));
|
||||
|
||||
std::vector<u8> acid_header_data = file->ReadBytes(sizeof(AcidHeader), npdm_header.acid_offset);
|
||||
if (sizeof(AcidHeader) != acid_header_data.size())
|
||||
return Loader::ResultStatus::Error;
|
||||
std::memcpy(&acid_header, acid_header_data.data(), sizeof(AcidHeader));
|
||||
size_t aci_offset = header_offset + npdm_header.aci_offset;
|
||||
size_t acid_offset = header_offset + npdm_header.acid_offset;
|
||||
memcpy(&aci_header, &file_data[aci_offset], sizeof(AciHeader));
|
||||
memcpy(&acid_header, &file_data[acid_offset], sizeof(AcidHeader));
|
||||
|
||||
if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset))
|
||||
return Loader::ResultStatus::Error;
|
||||
if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
|
||||
return Loader::ResultStatus::Error;
|
||||
size_t fac_offset = acid_offset + acid_header.fac_offset;
|
||||
size_t fah_offset = aci_offset + aci_header.fah_offset;
|
||||
memcpy(&acid_file_access, &file_data[fac_offset], sizeof(FileAccessControl));
|
||||
memcpy(&aci_file_access, &file_data[fah_offset], sizeof(FileAccessHeader));
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "partition_filesystem.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus;
|
||||
@@ -38,7 +37,8 @@ enum class ProgramFilePermission : u64 {
|
||||
*/
|
||||
class ProgramMetadata {
|
||||
public:
|
||||
Loader::ResultStatus Load(VirtualFile file);
|
||||
Loader::ResultStatus Load(const std::string& file_path);
|
||||
Loader::ResultStatus Load(const std::vector<u8> file_data, size_t offset = 0);
|
||||
|
||||
bool Is64BitProgram() const;
|
||||
ProgramAddressSpaceType GetAddressSpaceType() const;
|
||||
@@ -51,7 +51,6 @@ public:
|
||||
void Print() const;
|
||||
|
||||
private:
|
||||
// TODO(DarkLordZach): BitField is not trivially copyable.
|
||||
struct Header {
|
||||
std::array<char, 4> magic;
|
||||
std::array<u8, 8> reserved;
|
||||
@@ -78,7 +77,6 @@ private:
|
||||
|
||||
static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong");
|
||||
|
||||
// TODO(DarkLordZach): BitField is not trivially copyable.
|
||||
struct AcidHeader {
|
||||
std::array<u8, 0x100> signature;
|
||||
std::array<u8, 0x100> nca_modulus;
|
||||
|
||||
38
src/core/file_sys/romfs_factory.cpp
Normal file
38
src/core/file_sys/romfs_factory.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/romfs_filesystem.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
RomFS_Factory::RomFS_Factory(Loader::AppLoader& app_loader) {
|
||||
// Load the RomFS from the app
|
||||
if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) {
|
||||
LOG_ERROR(Service_FS, "Unable to read RomFS!");
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> RomFS_Factory::Open(const Path& path) {
|
||||
auto archive = std::make_unique<RomFS_FileSystem>(romfs_file, data_offset, data_size);
|
||||
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
|
||||
}
|
||||
|
||||
ResultCode RomFS_Factory::Format(const Path& path) {
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> RomFS_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
35
src/core/file_sys/romfs_factory.h
Normal file
35
src/core/file_sys/romfs_factory.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the RomFS archive
|
||||
class RomFS_Factory final : public FileSystemFactory {
|
||||
public:
|
||||
explicit RomFS_Factory(Loader::AppLoader& app_loader);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "ArchiveFactory_RomFS";
|
||||
}
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
|
||||
ResultCode Format(const Path& path) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<FileUtil::IOFile> romfs_file;
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
110
src/core/file_sys/romfs_filesystem.cpp
Normal file
110
src/core/file_sys/romfs_filesystem.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/romfs_filesystem.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
std::string RomFS_FileSystem::GetName() const {
|
||||
return "RomFS";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std::string& path,
|
||||
Mode mode) const {
|
||||
return MakeResult<std::unique_ptr<StorageBackend>>(
|
||||
std::make_unique<RomFS_Storage>(romfs_file, data_offset, data_size));
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::DeleteFile(const std::string& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive ({}).", GetName());
|
||||
// TODO(bunnei): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::RenameFile(const std::string& src_path,
|
||||
const std::string& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive ({}).", GetName());
|
||||
// TODO(bunnei): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory(
|
||||
const std::string& path) const {
|
||||
LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive");
|
||||
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>());
|
||||
}
|
||||
|
||||
u64 RomFS_FileSystem::GetFreeSpaceSize() const {
|
||||
LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path {}).", path);
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
|
||||
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
|
||||
romfs_file->Seek(data_offset + offset, SEEK_SET);
|
||||
size_t read_length = (size_t)std::min((u64)length, data_size - offset);
|
||||
|
||||
return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length));
|
||||
}
|
||||
|
||||
ResultVal<size_t> RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush,
|
||||
const u8* buffer) const {
|
||||
LOG_ERROR(Service_FS, "Attempted to write to ROMFS file");
|
||||
// TODO(Subv): Find error code
|
||||
return MakeResult<size_t>(0);
|
||||
}
|
||||
|
||||
u64 RomFS_Storage::GetSize() const {
|
||||
return data_size;
|
||||
}
|
||||
|
||||
bool RomFS_Storage::SetSize(const u64 size) const {
|
||||
LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
85
src/core/file_sys/romfs_filesystem.h
Normal file
85
src/core/file_sys/romfs_filesystem.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/storage.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/**
|
||||
* Helper which implements an interface to deal with Switch .istorage ROMFS images used in some
|
||||
* archives This should be subclassed by concrete archive types, which will provide the input data
|
||||
* (load the raw ROMFS archive) and override any required methods
|
||||
*/
|
||||
class RomFS_FileSystem : public FileSystemBackend {
|
||||
public:
|
||||
RomFS_FileSystem(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
|
||||
: romfs_file(file), data_offset(offset), data_size(size) {}
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
|
||||
Mode mode) const override;
|
||||
ResultCode DeleteFile(const std::string& path) const override;
|
||||
ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
|
||||
ResultCode DeleteDirectory(const Path& path) const override;
|
||||
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||
ResultCode CreateFile(const std::string& path, u64 size) const override;
|
||||
ResultCode CreateDirectory(const std::string& path) const override;
|
||||
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
|
||||
const std::string& path) const override;
|
||||
u64 GetFreeSpaceSize() const override;
|
||||
ResultVal<EntryType> GetEntryType(const std::string& path) const override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<FileUtil::IOFile> romfs_file;
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
};
|
||||
|
||||
class RomFS_Storage : public StorageBackend {
|
||||
public:
|
||||
RomFS_Storage(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
|
||||
: romfs_file(file), data_offset(offset), data_size(size) {}
|
||||
|
||||
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
|
||||
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
|
||||
u64 GetSize() const override;
|
||||
bool SetSize(u64 size) const override;
|
||||
bool Close() const override {
|
||||
return false;
|
||||
}
|
||||
void Flush() const override {}
|
||||
|
||||
private:
|
||||
std::shared_ptr<FileUtil::IOFile> romfs_file;
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
};
|
||||
|
||||
class ROMFSDirectory : public DirectoryBackend {
|
||||
public:
|
||||
u64 Read(const u64 count, Entry* entries) override {
|
||||
return 0;
|
||||
}
|
||||
u64 GetEntryCount() const override {
|
||||
return 0;
|
||||
}
|
||||
bool Close() const override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
63
src/core/file_sys/savedata_factory.cpp
Normal file
63
src/core/file_sys/savedata_factory.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/disk_filesystem.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
SaveData_Factory::SaveData_Factory(std::string nand_directory)
|
||||
: nand_directory(std::move(nand_directory)) {}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path& path) {
|
||||
std::string save_directory = GetFullPath();
|
||||
|
||||
if (!FileUtil::Exists(save_directory)) {
|
||||
// TODO(bunnei): This is a work-around to always create a save data directory if it does not
|
||||
// already exist. This is a hack, as we do not understand yet how this works on hardware.
|
||||
// Without a save data directory, many games will assert on boot. This should not have any
|
||||
// bad side-effects.
|
||||
FileUtil::CreateFullPath(save_directory);
|
||||
}
|
||||
|
||||
// Return an error if the save data doesn't actually exist.
|
||||
if (!FileUtil::IsDirectory(save_directory)) {
|
||||
// TODO(Subv): Find out correct error code.
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
auto archive = std::make_unique<Disk_FileSystem>(save_directory);
|
||||
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
|
||||
}
|
||||
|
||||
ResultCode SaveData_Factory::Format(const Path& path) {
|
||||
LOG_WARNING(Service_FS, "Format archive {}", GetName());
|
||||
// Create the save data directory.
|
||||
if (!FileUtil::CreateFullPath(GetFullPath())) {
|
||||
// TODO(Subv): Find the correct error code.
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> SaveData_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
std::string SaveData_Factory::GetFullPath() const {
|
||||
u64 title_id = Core::CurrentProcess()->program_id;
|
||||
// TODO(Subv): Somehow obtain this value.
|
||||
u32 user = 0;
|
||||
return fmt::format("{}save/{:016X}/{:08X}/", nand_directory, title_id, user);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
33
src/core/file_sys/savedata_factory.h
Normal file
33
src/core/file_sys/savedata_factory.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the SaveData archive
|
||||
class SaveData_Factory final : public FileSystemFactory {
|
||||
public:
|
||||
explicit SaveData_Factory(std::string nand_directory);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SaveData_Factory";
|
||||
}
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
|
||||
ResultCode Format(const Path& path) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
|
||||
|
||||
private:
|
||||
std::string nand_directory;
|
||||
|
||||
std::string GetFullPath() const;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
39
src/core/file_sys/sdmc_factory.cpp
Normal file
39
src/core/file_sys/sdmc_factory.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/disk_filesystem.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) {
|
||||
// Create the SD Card directory if it doesn't already exist.
|
||||
if (!FileUtil::IsDirectory(sd_directory)) {
|
||||
FileUtil::CreateFullPath(sd_directory);
|
||||
}
|
||||
|
||||
auto archive = std::make_unique<Disk_FileSystem>(sd_directory);
|
||||
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
|
||||
}
|
||||
|
||||
ResultCode SDMC_Factory::Format(const Path& path) {
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName());
|
||||
// TODO(Subv): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
31
src/core/file_sys/sdmc_factory.h
Normal file
31
src/core/file_sys/sdmc_factory.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the SDCard archive
|
||||
class SDMC_Factory final : public FileSystemFactory {
|
||||
public:
|
||||
explicit SDMC_Factory(std::string sd_directory);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SDMC_Factory";
|
||||
}
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
|
||||
ResultCode Format(const Path& path) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
|
||||
|
||||
private:
|
||||
std::string sd_directory;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -1,187 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VfsFile::~VfsFile() = default;
|
||||
|
||||
std::string VfsFile::GetExtension() const {
|
||||
return FileUtil::GetExtensionFromFilename(GetName());
|
||||
}
|
||||
|
||||
VfsDirectory::~VfsDirectory() = default;
|
||||
|
||||
boost::optional<u8> VfsFile::ReadByte(size_t offset) const {
|
||||
u8 out{};
|
||||
size_t size = Read(&out, 1, offset);
|
||||
if (size == 1)
|
||||
return out;
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
std::vector<u8> VfsFile::ReadBytes(size_t size, size_t offset) const {
|
||||
std::vector<u8> out(size);
|
||||
size_t read_size = Read(out.data(), size, offset);
|
||||
out.resize(read_size);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<u8> VfsFile::ReadAllBytes() const {
|
||||
return ReadBytes(GetSize());
|
||||
}
|
||||
|
||||
bool VfsFile::WriteByte(u8 data, size_t offset) {
|
||||
return Write(&data, 1, offset) == 1;
|
||||
}
|
||||
|
||||
size_t VfsFile::WriteBytes(std::vector<u8> data, size_t offset) {
|
||||
return Write(data.data(), data.size(), offset);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(const std::string& path) const {
|
||||
auto vec = FileUtil::SplitPathComponents(path);
|
||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
||||
vec.end());
|
||||
if (vec.empty())
|
||||
return nullptr;
|
||||
if (vec.size() == 1)
|
||||
return GetFile(vec[0]);
|
||||
auto dir = GetSubdirectory(vec[0]);
|
||||
for (size_t i = 1; i < vec.size() - 1; ++i) {
|
||||
if (dir == nullptr)
|
||||
return nullptr;
|
||||
dir = dir->GetSubdirectory(vec[i]);
|
||||
}
|
||||
if (dir == nullptr)
|
||||
return nullptr;
|
||||
return dir->GetFile(vec.back());
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(const std::string& path) const {
|
||||
if (IsRoot())
|
||||
return GetFileRelative(path);
|
||||
|
||||
return GetParentDirectory()->GetFileAbsolute(path);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(const std::string& path) const {
|
||||
auto vec = FileUtil::SplitPathComponents(path);
|
||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
||||
vec.end());
|
||||
if (vec.empty())
|
||||
// return std::shared_ptr<VfsDirectory>(this);
|
||||
return nullptr;
|
||||
auto dir = GetSubdirectory(vec[0]);
|
||||
for (size_t i = 1; i < vec.size(); ++i) {
|
||||
dir = dir->GetSubdirectory(vec[i]);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(const std::string& path) const {
|
||||
if (IsRoot())
|
||||
return GetDirectoryRelative(path);
|
||||
|
||||
return GetParentDirectory()->GetDirectoryAbsolute(path);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VfsDirectory::GetFile(const std::string& name) const {
|
||||
const auto& files = GetFiles();
|
||||
const auto iter = std::find_if(files.begin(), files.end(),
|
||||
[&name](const auto& file1) { return name == file1->GetName(); });
|
||||
return iter == files.end() ? nullptr : *iter;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VfsDirectory::GetSubdirectory(const std::string& name) const {
|
||||
const auto& subs = GetSubdirectories();
|
||||
const auto iter = std::find_if(subs.begin(), subs.end(),
|
||||
[&name](const auto& file1) { return name == file1->GetName(); });
|
||||
return iter == subs.end() ? nullptr : *iter;
|
||||
}
|
||||
|
||||
bool VfsDirectory::IsRoot() const {
|
||||
return GetParentDirectory() == nullptr;
|
||||
}
|
||||
|
||||
size_t VfsDirectory::GetSize() const {
|
||||
const auto& files = GetFiles();
|
||||
const auto file_total =
|
||||
std::accumulate(files.begin(), files.end(), 0ull,
|
||||
[](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
|
||||
|
||||
const auto& sub_dir = GetSubdirectories();
|
||||
const auto subdir_total =
|
||||
std::accumulate(sub_dir.begin(), sub_dir.end(), 0ull,
|
||||
[](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
|
||||
|
||||
return file_total + subdir_total;
|
||||
}
|
||||
|
||||
bool VfsDirectory::DeleteSubdirectoryRecursive(const std::string& name) {
|
||||
auto dir = GetSubdirectory(name);
|
||||
if (dir == nullptr)
|
||||
return false;
|
||||
|
||||
bool success = true;
|
||||
for (const auto& file : dir->GetFiles()) {
|
||||
if (!DeleteFile(file->GetName()))
|
||||
success = false;
|
||||
}
|
||||
|
||||
for (const auto& sdir : dir->GetSubdirectories()) {
|
||||
if (!dir->DeleteSubdirectoryRecursive(sdir->GetName()))
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VfsDirectory::Copy(const std::string& src, const std::string& dest) {
|
||||
const auto f1 = GetFile(src);
|
||||
auto f2 = CreateFile(dest);
|
||||
if (f1 == nullptr || f2 == nullptr)
|
||||
return false;
|
||||
|
||||
if (!f2->Resize(f1->GetSize())) {
|
||||
DeleteFile(dest);
|
||||
return false;
|
||||
}
|
||||
|
||||
return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectory::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectory::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateSubdirectory(const std::string& name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(const std::string& name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectory::DeleteSubdirectory(const std::string& name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectory::DeleteFile(const std::string& name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectory::Rename(const std::string& name) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
@@ -1,220 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "boost/optional.hpp"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
|
||||
namespace FileSys {
|
||||
struct VfsFile;
|
||||
struct VfsDirectory;
|
||||
|
||||
// Convenience typedefs to use VfsDirectory and VfsFile
|
||||
using VirtualDir = std::shared_ptr<FileSys::VfsDirectory>;
|
||||
using VirtualFile = std::shared_ptr<FileSys::VfsFile>;
|
||||
|
||||
// A class representing a file in an abstract filesystem.
|
||||
struct VfsFile : NonCopyable {
|
||||
virtual ~VfsFile();
|
||||
|
||||
// Retrieves the file name.
|
||||
virtual std::string GetName() const = 0;
|
||||
// Retrieves the extension of the file name.
|
||||
virtual std::string GetExtension() const;
|
||||
// Retrieves the size of the file.
|
||||
virtual size_t GetSize() const = 0;
|
||||
// Resizes the file to new_size. Returns whether or not the operation was successful.
|
||||
virtual bool Resize(size_t new_size) = 0;
|
||||
// Gets a pointer to the directory containing this file, returning nullptr if there is none.
|
||||
virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0;
|
||||
|
||||
// Returns whether or not the file can be written to.
|
||||
virtual bool IsWritable() const = 0;
|
||||
// Returns whether or not the file can be read from.
|
||||
virtual bool IsReadable() const = 0;
|
||||
|
||||
// The primary method of reading from the file. Reads length bytes into data starting at offset
|
||||
// into file. Returns number of bytes successfully read.
|
||||
virtual size_t Read(u8* data, size_t length, size_t offset = 0) const = 0;
|
||||
// The primary method of writing to the file. Writes length bytes from data starting at offset
|
||||
// into file. Returns number of bytes successfully written.
|
||||
virtual size_t Write(const u8* data, size_t length, size_t offset = 0) = 0;
|
||||
|
||||
// Reads exactly one byte at the offset provided, returning boost::none on error.
|
||||
virtual boost::optional<u8> ReadByte(size_t offset = 0) const;
|
||||
// Reads size bytes starting at offset in file into a vector.
|
||||
virtual std::vector<u8> ReadBytes(size_t size, size_t offset = 0) const;
|
||||
// Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
|
||||
// 0)'
|
||||
virtual std::vector<u8> ReadAllBytes() const;
|
||||
|
||||
// Reads an array of type T, size number_elements starting at offset.
|
||||
// Returns the number of bytes (sizeof(T)*number_elements) read successfully.
|
||||
template <typename T>
|
||||
size_t ReadArray(T* data, size_t number_elements, size_t offset = 0) const {
|
||||
static_assert(std::is_trivially_copyable<T>::value,
|
||||
"Data type must be trivially copyable.");
|
||||
|
||||
return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset);
|
||||
}
|
||||
|
||||
// Reads size bytes into the memory starting at data starting at offset into the file.
|
||||
// Returns the number of bytes read successfully.
|
||||
template <typename T>
|
||||
size_t ReadBytes(T* data, size_t size, size_t offset = 0) const {
|
||||
static_assert(std::is_trivially_copyable<T>::value,
|
||||
"Data type must be trivially copyable.");
|
||||
return Read(reinterpret_cast<u8*>(data), size, offset);
|
||||
}
|
||||
|
||||
// Reads one object of type T starting at offset in file.
|
||||
// Returns the number of bytes read successfully (sizeof(T)).
|
||||
template <typename T>
|
||||
size_t ReadObject(T* data, size_t offset = 0) const {
|
||||
static_assert(std::is_trivially_copyable<T>::value,
|
||||
"Data type must be trivially copyable.");
|
||||
return Read(reinterpret_cast<u8*>(data), sizeof(T), offset);
|
||||
}
|
||||
|
||||
// Writes exactly one byte to offset in file and retuns whether or not the byte was written
|
||||
// successfully.
|
||||
virtual bool WriteByte(u8 data, size_t offset = 0);
|
||||
// Writes a vector of bytes to offset in file and returns the number of bytes successfully
|
||||
// written.
|
||||
virtual size_t WriteBytes(std::vector<u8> data, size_t offset = 0);
|
||||
|
||||
// Writes an array of type T, size number_elements to offset in file.
|
||||
// Returns the number of bytes (sizeof(T)*number_elements) written successfully.
|
||||
template <typename T>
|
||||
size_t WriteArray(T* data, size_t number_elements, size_t offset = 0) {
|
||||
static_assert(std::is_trivially_copyable<T>::value,
|
||||
"Data type must be trivially copyable.");
|
||||
|
||||
return Write(data, number_elements * sizeof(T), offset);
|
||||
}
|
||||
|
||||
// Writes size bytes starting at memory location data to offset in file.
|
||||
// Returns the number of bytes written successfully.
|
||||
template <typename T>
|
||||
size_t WriteBytes(T* data, size_t size, size_t offset = 0) {
|
||||
static_assert(std::is_trivially_copyable<T>::value,
|
||||
"Data type must be trivially copyable.");
|
||||
return Write(reinterpret_cast<u8*>(data), size, offset);
|
||||
}
|
||||
|
||||
// Writes one object of type T to offset in file.
|
||||
// Returns the number of bytes written successfully (sizeof(T)).
|
||||
template <typename T>
|
||||
size_t WriteObject(const T& data, size_t offset = 0) {
|
||||
static_assert(std::is_trivially_copyable<T>::value,
|
||||
"Data type must be trivially copyable.");
|
||||
return Write(&data, sizeof(T), offset);
|
||||
}
|
||||
|
||||
// Renames the file to name. Returns whether or not the operation was successsful.
|
||||
virtual bool Rename(const std::string& name) = 0;
|
||||
};
|
||||
|
||||
// A class representing a directory in an abstract filesystem.
|
||||
struct VfsDirectory : NonCopyable {
|
||||
virtual ~VfsDirectory();
|
||||
|
||||
// Retrives the file located at path as if the current directory was root. Returns nullptr if
|
||||
// not found.
|
||||
virtual std::shared_ptr<VfsFile> GetFileRelative(const std::string& path) const;
|
||||
// Calls GetFileRelative(path) on the root of the current directory.
|
||||
virtual std::shared_ptr<VfsFile> GetFileAbsolute(const std::string& path) const;
|
||||
|
||||
// Retrives the directory located at path as if the current directory was root. Returns nullptr
|
||||
// if not found.
|
||||
virtual std::shared_ptr<VfsDirectory> GetDirectoryRelative(const std::string& path) const;
|
||||
// Calls GetDirectoryRelative(path) on the root of the current directory.
|
||||
virtual std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(const std::string& path) const;
|
||||
|
||||
// Returns a vector containing all of the files in this directory.
|
||||
virtual std::vector<std::shared_ptr<VfsFile>> GetFiles() const = 0;
|
||||
// Returns the file with filename matching name. Returns nullptr if directory dosen't have a
|
||||
// file with name.
|
||||
virtual std::shared_ptr<VfsFile> GetFile(const std::string& name) const;
|
||||
|
||||
// Returns a vector containing all of the subdirectories in this directory.
|
||||
virtual std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const = 0;
|
||||
// Returns the directory with name matching name. Returns nullptr if directory dosen't have a
|
||||
// directory with name.
|
||||
virtual std::shared_ptr<VfsDirectory> GetSubdirectory(const std::string& name) const;
|
||||
|
||||
// Returns whether or not the directory can be written to.
|
||||
virtual bool IsWritable() const = 0;
|
||||
// Returns whether of not the directory can be read from.
|
||||
virtual bool IsReadable() const = 0;
|
||||
|
||||
// Returns whether or not the directory is the root of the current file tree.
|
||||
virtual bool IsRoot() const;
|
||||
|
||||
// Returns the name of the directory.
|
||||
virtual std::string GetName() const = 0;
|
||||
// Returns the total size of all files and subdirectories in this directory.
|
||||
virtual size_t GetSize() const;
|
||||
// Returns the parent directory of this directory. Returns nullptr if this directory is root or
|
||||
// has no parent.
|
||||
virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0;
|
||||
|
||||
// Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
|
||||
// if the operation failed.
|
||||
virtual std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) = 0;
|
||||
// Creates a new file with name name. Returns a pointer to the new file or nullptr if the
|
||||
// operation failed.
|
||||
virtual std::shared_ptr<VfsFile> CreateFile(const std::string& name) = 0;
|
||||
|
||||
// Deletes the subdirectory with name and returns true on success.
|
||||
virtual bool DeleteSubdirectory(const std::string& name) = 0;
|
||||
// Deletes all subdirectories and files of subdirectory with name recirsively and then deletes
|
||||
// the subdirectory. Returns true on success.
|
||||
virtual bool DeleteSubdirectoryRecursive(const std::string& name);
|
||||
// Returnes whether or not the file with name name was deleted successfully.
|
||||
virtual bool DeleteFile(const std::string& name) = 0;
|
||||
|
||||
// Returns whether or not this directory was renamed to name.
|
||||
virtual bool Rename(const std::string& name) = 0;
|
||||
|
||||
// Returns whether or not the file with name src was successfully copied to a new file with name
|
||||
// dest.
|
||||
virtual bool Copy(const std::string& src, const std::string& dest);
|
||||
|
||||
// Interprets the file with name file instead as a directory of type directory.
|
||||
// The directory must have a constructor that takes a single argument of type
|
||||
// std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
|
||||
// subdirectory in one call.
|
||||
template <typename Directory>
|
||||
bool InterpretAsDirectory(const std::string& file) {
|
||||
auto file_p = GetFile(file);
|
||||
if (file_p == nullptr)
|
||||
return false;
|
||||
return ReplaceFileWithSubdirectory(file, std::make_shared<Directory>(file_p));
|
||||
}
|
||||
|
||||
protected:
|
||||
// Backend for InterpretAsDirectory.
|
||||
// Removes all references to file and adds a reference to dir in the directory's implementation.
|
||||
virtual bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) = 0;
|
||||
};
|
||||
|
||||
// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
|
||||
// if writable. This is to avoid redundant empty methods everywhere.
|
||||
struct ReadOnlyVfsDirectory : public VfsDirectory {
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) override;
|
||||
std::shared_ptr<VfsFile> CreateFile(const std::string& name) override;
|
||||
bool DeleteSubdirectory(const std::string& name) override;
|
||||
bool DeleteFile(const std::string& name) override;
|
||||
bool Rename(const std::string& name) override;
|
||||
};
|
||||
} // namespace FileSys
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, size_t size_, size_t offset_,
|
||||
const std::string& name_)
|
||||
: file(file_), offset(offset_), size(size_), name(name_) {}
|
||||
|
||||
std::string OffsetVfsFile::GetName() const {
|
||||
return name.empty() ? file->GetName() : name;
|
||||
}
|
||||
|
||||
size_t OffsetVfsFile::GetSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
bool OffsetVfsFile::Resize(size_t new_size) {
|
||||
if (offset + new_size < file->GetSize()) {
|
||||
size = new_size;
|
||||
} else {
|
||||
auto res = file->Resize(offset + new_size);
|
||||
if (!res)
|
||||
return false;
|
||||
size = new_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool OffsetVfsFile::IsWritable() const {
|
||||
return file->IsWritable();
|
||||
}
|
||||
|
||||
bool OffsetVfsFile::IsReadable() const {
|
||||
return file->IsReadable();
|
||||
}
|
||||
|
||||
size_t OffsetVfsFile::Read(u8* data, size_t length, size_t r_offset) const {
|
||||
return file->Read(data, TrimToFit(length, r_offset), offset + r_offset);
|
||||
}
|
||||
|
||||
size_t OffsetVfsFile::Write(const u8* data, size_t length, size_t r_offset) {
|
||||
return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
|
||||
}
|
||||
|
||||
boost::optional<u8> OffsetVfsFile::ReadByte(size_t r_offset) const {
|
||||
if (r_offset < size)
|
||||
return file->ReadByte(offset + r_offset);
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
std::vector<u8> OffsetVfsFile::ReadBytes(size_t r_size, size_t r_offset) const {
|
||||
return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset);
|
||||
}
|
||||
|
||||
std::vector<u8> OffsetVfsFile::ReadAllBytes() const {
|
||||
return file->ReadBytes(size, offset);
|
||||
}
|
||||
|
||||
bool OffsetVfsFile::WriteByte(u8 data, size_t r_offset) {
|
||||
if (r_offset < size)
|
||||
return file->WriteByte(data, offset + r_offset);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t OffsetVfsFile::WriteBytes(std::vector<u8> data, size_t r_offset) {
|
||||
return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset);
|
||||
}
|
||||
|
||||
bool OffsetVfsFile::Rename(const std::string& name) {
|
||||
return file->Rename(name);
|
||||
}
|
||||
|
||||
size_t OffsetVfsFile::GetOffset() const {
|
||||
return offset;
|
||||
}
|
||||
|
||||
size_t OffsetVfsFile::TrimToFit(size_t r_size, size_t r_offset) const {
|
||||
return std::max<size_t>(std::min<size_t>(size - r_offset, r_size), 0);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// An implementation of VfsFile that wraps around another VfsFile at a certain offset.
|
||||
// Similar to seeking to an offset.
|
||||
// If the file is writable, operations that would write past the end of the offset file will expand
|
||||
// the size of this wrapper.
|
||||
struct OffsetVfsFile : public VfsFile {
|
||||
OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0,
|
||||
const std::string& new_name = "");
|
||||
|
||||
std::string GetName() const override;
|
||||
size_t GetSize() const override;
|
||||
bool Resize(size_t new_size) override;
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
size_t Write(const u8* data, size_t length, size_t offset) override;
|
||||
boost::optional<u8> ReadByte(size_t offset) const override;
|
||||
std::vector<u8> ReadBytes(size_t size, size_t offset) const override;
|
||||
std::vector<u8> ReadAllBytes() const override;
|
||||
bool WriteByte(u8 data, size_t offset) override;
|
||||
size_t WriteBytes(std::vector<u8> data, size_t offset) override;
|
||||
|
||||
bool Rename(const std::string& name) override;
|
||||
|
||||
size_t GetOffset() const;
|
||||
|
||||
private:
|
||||
size_t TrimToFit(size_t r_size, size_t r_offset) const;
|
||||
|
||||
std::shared_ptr<VfsFile> file;
|
||||
size_t offset;
|
||||
size_t size;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -1,168 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
static std::string PermissionsToCharArray(Mode perms) {
|
||||
std::string out;
|
||||
switch (perms) {
|
||||
case Mode::Read:
|
||||
out += "r";
|
||||
break;
|
||||
case Mode::Write:
|
||||
out += "r+";
|
||||
break;
|
||||
case Mode::Append:
|
||||
out += "a";
|
||||
break;
|
||||
}
|
||||
return out + "b";
|
||||
}
|
||||
|
||||
RealVfsFile::RealVfsFile(const std::string& path_, Mode perms_)
|
||||
: backing(path_, PermissionsToCharArray(perms_).c_str()), path(path_),
|
||||
parent_path(FileUtil::GetParentPath(path_)),
|
||||
path_components(FileUtil::SplitPathComponents(path_)),
|
||||
parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
|
||||
perms(perms_) {}
|
||||
|
||||
std::string RealVfsFile::GetName() const {
|
||||
return path_components.back();
|
||||
}
|
||||
|
||||
size_t RealVfsFile::GetSize() const {
|
||||
return backing.GetSize();
|
||||
}
|
||||
|
||||
bool RealVfsFile::Resize(size_t new_size) {
|
||||
return backing.Resize(new_size);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
|
||||
return std::make_shared<RealVfsDirectory>(parent_path, perms);
|
||||
}
|
||||
|
||||
bool RealVfsFile::IsWritable() const {
|
||||
return perms == Mode::Append || perms == Mode::Write;
|
||||
}
|
||||
|
||||
bool RealVfsFile::IsReadable() const {
|
||||
return perms == Mode::Read || perms == Mode::Write;
|
||||
}
|
||||
|
||||
size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const {
|
||||
if (!backing.Seek(offset, SEEK_SET))
|
||||
return 0;
|
||||
return backing.ReadBytes(data, length);
|
||||
}
|
||||
|
||||
size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) {
|
||||
if (!backing.Seek(offset, SEEK_SET))
|
||||
return 0;
|
||||
return backing.WriteBytes(data, length);
|
||||
}
|
||||
|
||||
bool RealVfsFile::Rename(const std::string& name) {
|
||||
const auto out = FileUtil::Rename(GetName(), name);
|
||||
path = parent_path + DIR_SEP + name;
|
||||
path_components = parent_components;
|
||||
path_components.push_back(name);
|
||||
backing = FileUtil::IOFile(path, PermissionsToCharArray(perms).c_str());
|
||||
return out;
|
||||
}
|
||||
|
||||
RealVfsDirectory::RealVfsDirectory(const std::string& path_, Mode perms_)
|
||||
: path(FileUtil::RemoveTrailingSlash(path_)), parent_path(FileUtil::GetParentPath(path)),
|
||||
path_components(FileUtil::SplitPathComponents(path)),
|
||||
parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
|
||||
perms(perms_) {
|
||||
if (!FileUtil::Exists(path) && (perms == Mode::Write || perms == Mode::Append))
|
||||
FileUtil::CreateDir(path);
|
||||
unsigned size;
|
||||
if (perms != Mode::Append) {
|
||||
FileUtil::ForeachDirectoryEntry(
|
||||
&size, path,
|
||||
[this](unsigned* entries_out, const std::string& directory,
|
||||
const std::string& filename) {
|
||||
std::string full_path = directory + DIR_SEP + filename;
|
||||
if (FileUtil::IsDirectory(full_path))
|
||||
subdirectories.emplace_back(
|
||||
std::make_shared<RealVfsDirectory>(full_path, perms));
|
||||
else
|
||||
files.emplace_back(std::make_shared<RealVfsFile>(full_path, perms));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const {
|
||||
return std::vector<std::shared_ptr<VfsFile>>(files);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const {
|
||||
return std::vector<std::shared_ptr<VfsDirectory>>(subdirectories);
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::IsWritable() const {
|
||||
return perms == Mode::Write || perms == Mode::Append;
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::IsReadable() const {
|
||||
return perms == Mode::Read || perms == Mode::Write;
|
||||
}
|
||||
|
||||
std::string RealVfsDirectory::GetName() const {
|
||||
return path_components.back();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
|
||||
if (path_components.size() <= 1)
|
||||
return nullptr;
|
||||
|
||||
return std::make_shared<RealVfsDirectory>(parent_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(const std::string& name) {
|
||||
if (!FileUtil::CreateDir(path + DIR_SEP + name))
|
||||
return nullptr;
|
||||
subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(path + DIR_SEP + name, perms));
|
||||
return subdirectories.back();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(const std::string& name) {
|
||||
if (!FileUtil::CreateEmptyFile(path + DIR_SEP + name))
|
||||
return nullptr;
|
||||
files.emplace_back(std::make_shared<RealVfsFile>(path + DIR_SEP + name, perms));
|
||||
return files.back();
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::DeleteSubdirectory(const std::string& name) {
|
||||
return FileUtil::DeleteDirRecursively(path + DIR_SEP + name);
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::DeleteFile(const std::string& name) {
|
||||
return FileUtil::Delete(path + DIR_SEP + name);
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::Rename(const std::string& name) {
|
||||
return FileUtil::Rename(path, parent_path + DIR_SEP + name);
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
auto iter = std::find(files.begin(), files.end(), file);
|
||||
if (iter == files.end())
|
||||
return false;
|
||||
|
||||
files[iter - files.begin()] = files.back();
|
||||
files.pop_back();
|
||||
|
||||
subdirectories.emplace_back(dir);
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace FileSys
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// An implmentation of VfsFile that represents a file on the user's computer.
|
||||
struct RealVfsFile : public VfsFile {
|
||||
RealVfsFile(const std::string& name, Mode perms = Mode::Read);
|
||||
|
||||
std::string GetName() const override;
|
||||
size_t GetSize() const override;
|
||||
bool Resize(size_t new_size) override;
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
size_t Write(const u8* data, size_t length, size_t offset) override;
|
||||
bool Rename(const std::string& name) override;
|
||||
|
||||
private:
|
||||
FileUtil::IOFile backing;
|
||||
std::string path;
|
||||
std::string parent_path;
|
||||
std::vector<std::string> path_components;
|
||||
std::vector<std::string> parent_components;
|
||||
Mode perms;
|
||||
};
|
||||
|
||||
// An implementation of VfsDirectory that represents a directory on the user's computer.
|
||||
struct RealVfsDirectory : public VfsDirectory {
|
||||
RealVfsDirectory(const std::string& path, Mode perms);
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::string GetName() const override;
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) override;
|
||||
std::shared_ptr<VfsFile> CreateFile(const std::string& name) override;
|
||||
bool DeleteSubdirectory(const std::string& name) override;
|
||||
bool DeleteFile(const std::string& name) override;
|
||||
bool Rename(const std::string& name) override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
std::string path;
|
||||
std::string parent_path;
|
||||
std::vector<std::string> path_components;
|
||||
std::vector<std::string> parent_components;
|
||||
Mode perms;
|
||||
std::vector<std::shared_ptr<VfsFile>> files;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> subdirectories;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -61,10 +61,12 @@ const u32 SIGTERM = 15;
|
||||
const u32 MSG_WAITALL = 8;
|
||||
#endif
|
||||
|
||||
const u32 X30_REGISTER = 30;
|
||||
const u32 LR_REGISTER = 30;
|
||||
const u32 SP_REGISTER = 31;
|
||||
const u32 PC_REGISTER = 32;
|
||||
const u32 CPSR_REGISTER = 33;
|
||||
const u32 UC_ARM64_REG_Q0 = 34;
|
||||
const u32 FPSCR_REGISTER = 66;
|
||||
|
||||
// For sample XML files see the GDB source /gdb/features
|
||||
// GDB also wants the l character at the start
|
||||
@@ -130,6 +132,8 @@ static const char* target_xml =
|
||||
</flags>
|
||||
<reg name="cpsr" bitsize="32" type="cpsr_flags"/>
|
||||
</feature>
|
||||
<feature name="org.gnu.gdb.aarch64.fpu">
|
||||
</feature>
|
||||
</target>
|
||||
)";
|
||||
|
||||
@@ -144,6 +148,7 @@ static u32 latest_signal = 0;
|
||||
static bool memory_break = false;
|
||||
|
||||
static Kernel::Thread* current_thread = nullptr;
|
||||
static u32 current_core = 0;
|
||||
|
||||
// Binding to a port within the reserved ports range (0-1023) requires root permissions,
|
||||
// so default to a port outside of that range.
|
||||
@@ -171,13 +176,34 @@ static std::map<u64, Breakpoint> breakpoints_execute;
|
||||
static std::map<u64, Breakpoint> breakpoints_read;
|
||||
static std::map<u64, Breakpoint> breakpoints_write;
|
||||
|
||||
struct Module {
|
||||
std::string name;
|
||||
PAddr beg;
|
||||
PAddr end;
|
||||
};
|
||||
|
||||
static std::vector<Module> modules;
|
||||
|
||||
void RegisterModule(std::string name, PAddr beg, PAddr end, bool add_elf_ext) {
|
||||
Module module;
|
||||
if (add_elf_ext) {
|
||||
Common::SplitPath(name, nullptr, &module.name, nullptr);
|
||||
module.name += ".elf";
|
||||
} else {
|
||||
module.name = std::move(name);
|
||||
}
|
||||
module.beg = beg;
|
||||
module.end = end;
|
||||
modules.push_back(std::move(module));
|
||||
}
|
||||
|
||||
static Kernel::Thread* FindThreadById(int id) {
|
||||
for (int core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto thread : threads) {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadId() == id) {
|
||||
current_thread = thread.get();
|
||||
return current_thread;
|
||||
current_core = core;
|
||||
return thread.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,6 +223,8 @@ static u64 RegRead(int id, Kernel::Thread* thread = nullptr) {
|
||||
return thread->context.pc;
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
return thread->context.cpsr;
|
||||
} else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) {
|
||||
return thread->context.fpu_registers[id - UC_ARM64_REG_Q0][0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -215,6 +243,8 @@ static void RegWrite(int id, u64 val, Kernel::Thread* thread = nullptr) {
|
||||
thread->context.pc = val;
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
thread->context.cpsr = val;
|
||||
} else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) {
|
||||
thread->context.fpu_registers[id - (CPSR_REGISTER + 1)][0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,7 +564,11 @@ static void HandleQuery() {
|
||||
SendReply("T0");
|
||||
} else if (strncmp(query, "Supported", strlen("Supported")) == 0) {
|
||||
// PacketSize needs to be large enough for target xml
|
||||
SendReply("PacketSize=2000;qXfer:features:read+");
|
||||
std::string buffer = "PacketSize=2000;qXfer:features:read+;qXfer:threads:read+";
|
||||
if (modules.size()) {
|
||||
buffer += ";qXfer:libraries:read+";
|
||||
}
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "Xfer:features:read:target.xml:",
|
||||
strlen("Xfer:features:read:target.xml:")) == 0) {
|
||||
SendReply(target_xml);
|
||||
@@ -543,9 +577,9 @@ static void HandleQuery() {
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
|
||||
std::string val = "m";
|
||||
for (int core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto thread : threads) {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x}", thread->GetThreadId());
|
||||
val += ",";
|
||||
}
|
||||
@@ -554,6 +588,31 @@ static void HandleQuery() {
|
||||
SendReply(val.c_str());
|
||||
} else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) {
|
||||
SendReply("l");
|
||||
} else if (strncmp(query, "Xfer:threads:read", strlen("Xfer:threads:read")) == 0) {
|
||||
std::string buffer;
|
||||
buffer += "l<?xml version=\"1.0\"?>";
|
||||
buffer += "<threads>";
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
buffer +=
|
||||
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
|
||||
thread->GetThreadId(), core, thread->GetThreadId());
|
||||
}
|
||||
}
|
||||
buffer += "</threads>";
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "Xfer:libraries:read", strlen("Xfer:libraries:read")) == 0) {
|
||||
std::string buffer;
|
||||
buffer += "l<?xml version=\"1.0\"?>";
|
||||
buffer += "<library-list>";
|
||||
for (const auto& module : modules) {
|
||||
buffer +=
|
||||
fmt::format(R"*("<library name = "{}"><segment address = "0x{:x}"/></library>)*",
|
||||
module.name, module.beg);
|
||||
}
|
||||
buffer += "</library-list>";
|
||||
SendReply(buffer.c_str());
|
||||
} else {
|
||||
SendReply("");
|
||||
}
|
||||
@@ -561,33 +620,27 @@ static void HandleQuery() {
|
||||
|
||||
/// Handle set thread command from gdb client.
|
||||
static void HandleSetThread() {
|
||||
if (memcmp(command_buffer, "Hc", 2) == 0 || memcmp(command_buffer, "Hg", 2) == 0) {
|
||||
int thread_id = -1;
|
||||
if (command_buffer[2] != '-') {
|
||||
thread_id = static_cast<int>(HexToInt(
|
||||
command_buffer + 2,
|
||||
command_length - 2 /*strlen(reinterpret_cast<char*>(command_buffer) + 2)*/));
|
||||
}
|
||||
if (thread_id >= 1) {
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (!current_thread) {
|
||||
thread_id = 1;
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (current_thread) {
|
||||
SendReply("OK");
|
||||
return;
|
||||
}
|
||||
int thread_id = -1;
|
||||
if (command_buffer[2] != '-') {
|
||||
thread_id = static_cast<int>(HexToInt(command_buffer + 2, command_length - 2));
|
||||
}
|
||||
if (thread_id >= 1) {
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (!current_thread) {
|
||||
thread_id = 1;
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (current_thread) {
|
||||
SendReply("OK");
|
||||
return;
|
||||
}
|
||||
SendReply("E01");
|
||||
}
|
||||
|
||||
/// Handle thread alive command from gdb client.
|
||||
static void HandleThreadAlive() {
|
||||
int thread_id = static_cast<int>(
|
||||
HexToInt(command_buffer + 1,
|
||||
command_length - 1 /*strlen(reinterpret_cast<char*>(command_buffer) + 1)*/));
|
||||
int thread_id = static_cast<int>(HexToInt(command_buffer + 1, command_length - 1));
|
||||
if (thread_id == 0) {
|
||||
thread_id = 1;
|
||||
}
|
||||
@@ -610,16 +663,23 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
|
||||
|
||||
latest_signal = signal;
|
||||
|
||||
std::string buffer;
|
||||
if (full) {
|
||||
buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};", latest_signal, PC_REGISTER,
|
||||
Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER,
|
||||
Common::swap64(RegRead(SP_REGISTER, thread)));
|
||||
} else {
|
||||
buffer = fmt::format("T{:02x};", latest_signal);
|
||||
if (!thread) {
|
||||
full = false;
|
||||
}
|
||||
|
||||
buffer += fmt::format("thread:{:x};", thread->GetThreadId());
|
||||
std::string buffer;
|
||||
if (full) {
|
||||
buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};{:02x}:{:016x}", latest_signal,
|
||||
PC_REGISTER, Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER,
|
||||
Common::swap64(RegRead(SP_REGISTER, thread)), LR_REGISTER,
|
||||
Common::swap64(RegRead(LR_REGISTER, thread)));
|
||||
} else {
|
||||
buffer = fmt::format("T{:02x}", latest_signal);
|
||||
}
|
||||
|
||||
if (thread) {
|
||||
buffer += fmt::format(";thread:{:x};", thread->GetThreadId());
|
||||
}
|
||||
|
||||
SendReply(buffer.c_str());
|
||||
}
|
||||
@@ -711,8 +771,13 @@ static void ReadRegister() {
|
||||
LongToGdbHex(reply, RegRead(id, current_thread));
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
IntToGdbHex(reply, (u32)RegRead(id, current_thread));
|
||||
} else if (id >= UC_ARM64_REG_Q0 && id < FPSCR_REGISTER) {
|
||||
LongToGdbHex(reply, RegRead(id, current_thread));
|
||||
} else if (id == FPSCR_REGISTER) {
|
||||
LongToGdbHex(reply, RegRead(998, current_thread));
|
||||
} else {
|
||||
return SendReply("E01");
|
||||
// return SendReply("E01");
|
||||
LongToGdbHex(reply, RegRead(997, current_thread));
|
||||
}
|
||||
|
||||
SendReply(reinterpret_cast<char*>(reply));
|
||||
@@ -729,7 +794,7 @@ static void ReadRegisters() {
|
||||
LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread));
|
||||
}
|
||||
|
||||
bufptr += (32 * 16);
|
||||
bufptr += 32 * 16;
|
||||
|
||||
LongToGdbHex(bufptr, RegRead(PC_REGISTER, current_thread));
|
||||
|
||||
@@ -739,6 +804,16 @@ static void ReadRegisters() {
|
||||
|
||||
bufptr += 8;
|
||||
|
||||
for (int reg = UC_ARM64_REG_Q0; reg <= UC_ARM64_REG_Q0 + 31; reg++) {
|
||||
LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread));
|
||||
}
|
||||
|
||||
bufptr += 32 * 32;
|
||||
|
||||
LongToGdbHex(bufptr, RegRead(998, current_thread));
|
||||
|
||||
bufptr += 8;
|
||||
|
||||
SendReply(reinterpret_cast<char*>(buffer));
|
||||
}
|
||||
|
||||
@@ -759,10 +834,17 @@ static void WriteRegister() {
|
||||
RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
RegWrite(id, GdbHexToInt(buffer_ptr), current_thread);
|
||||
} else if (id >= UC_ARM64_REG_Q0 && id < FPSCR_REGISTER) {
|
||||
RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
|
||||
} else if (id == FPSCR_REGISTER) {
|
||||
RegWrite(998, GdbHexToLong(buffer_ptr), current_thread);
|
||||
} else {
|
||||
return SendReply("E01");
|
||||
// return SendReply("E01");
|
||||
RegWrite(997, GdbHexToLong(buffer_ptr), current_thread);
|
||||
}
|
||||
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
|
||||
SendReply("OK");
|
||||
}
|
||||
|
||||
@@ -773,18 +855,24 @@ static void WriteRegisters() {
|
||||
if (command_buffer[0] != 'G')
|
||||
return SendReply("E01");
|
||||
|
||||
for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) {
|
||||
for (int i = 0, reg = 0; reg <= FPSCR_REGISTER; i++, reg++) {
|
||||
if (reg <= SP_REGISTER) {
|
||||
RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg == PC_REGISTER) {
|
||||
RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg == CPSR_REGISTER) {
|
||||
RegWrite(CPSR_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg >= UC_ARM64_REG_Q0 && reg < FPSCR_REGISTER) {
|
||||
RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg == FPSCR_REGISTER) {
|
||||
RegWrite(998, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
}
|
||||
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
|
||||
SendReply("OK");
|
||||
}
|
||||
|
||||
@@ -806,6 +894,10 @@ static void ReadMemory() {
|
||||
SendReply("E01");
|
||||
}
|
||||
|
||||
if (addr < Memory::PROCESS_IMAGE_VADDR || addr >= Memory::MAP_REGION_VADDR_END) {
|
||||
return SendReply("E00");
|
||||
}
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(addr)) {
|
||||
return SendReply("E00");
|
||||
}
|
||||
@@ -840,16 +932,17 @@ static void WriteMemory() {
|
||||
}
|
||||
|
||||
void Break(bool is_memory_break) {
|
||||
if (!halt_loop) {
|
||||
halt_loop = true;
|
||||
send_trap = true;
|
||||
}
|
||||
send_trap = true;
|
||||
|
||||
memory_break = is_memory_break;
|
||||
}
|
||||
|
||||
/// Tell the CPU that it should perform a single step.
|
||||
static void Step() {
|
||||
if (command_length > 1) {
|
||||
RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
}
|
||||
step_loop = true;
|
||||
halt_loop = true;
|
||||
send_trap = true;
|
||||
@@ -1090,6 +1183,8 @@ static void Init(u16 port) {
|
||||
breakpoints_read.clear();
|
||||
breakpoints_write.clear();
|
||||
|
||||
modules.clear();
|
||||
|
||||
// Start gdb server
|
||||
LOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port);
|
||||
|
||||
@@ -1192,8 +1287,12 @@ void SetCpuStepFlag(bool is_step) {
|
||||
|
||||
void SendTrap(Kernel::Thread* thread, int trap) {
|
||||
if (send_trap) {
|
||||
if (!halt_loop || current_thread == thread) {
|
||||
current_thread = thread;
|
||||
SendSignal(thread, trap);
|
||||
}
|
||||
halt_loop = true;
|
||||
send_trap = false;
|
||||
SendSignal(thread, trap);
|
||||
}
|
||||
}
|
||||
}; // namespace GDBStub
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
@@ -51,6 +52,9 @@ bool IsServerEnabled();
|
||||
/// Returns true if there is an active socket connection.
|
||||
bool IsConnected();
|
||||
|
||||
/// Register module.
|
||||
void RegisterModule(std::string name, PAddr beg, PAddr end, bool add_elf_ext = true);
|
||||
|
||||
/**
|
||||
* Signal to the gdbstub server that it should halt CPU execution.
|
||||
*
|
||||
@@ -80,10 +84,10 @@ BreakpointAddress GetNextBreakpointFromAddress(PAddr addr, GDBStub::BreakpointTy
|
||||
*/
|
||||
bool CheckBreakpoint(PAddr addr, GDBStub::BreakpointType type);
|
||||
|
||||
// If set to true, the CPU will halt at the beginning of the next CPU loop.
|
||||
/// If set to true, the CPU will halt at the beginning of the next CPU loop.
|
||||
bool GetCpuHaltFlag();
|
||||
|
||||
// If set to true and the CPU is halted, the CPU will step one instruction.
|
||||
/// If set to true and the CPU is halted, the CPU will step one instruction.
|
||||
bool GetCpuStepFlag();
|
||||
|
||||
/**
|
||||
|
||||
@@ -621,7 +621,7 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
FileSys::Path unused;
|
||||
auto savedata = FileSystem::OpenFileSystem(FileSystem::Type::SaveData);
|
||||
auto savedata = FileSystem::OpenFileSystem(FileSystem::Type::SaveData, unused);
|
||||
if (savedata.Failed()) {
|
||||
// Create the save data and return an error indicating that the operation was performed.
|
||||
FileSystem::FormatFileSystem(FileSystem::Type::SaveData);
|
||||
|
||||
@@ -27,12 +27,12 @@ public:
|
||||
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
|
||||
{1, &IAudioOut::StartAudioOut, "StartAudioOut"},
|
||||
{2, &IAudioOut::StopAudioOut, "StopAudioOut"},
|
||||
{3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"},
|
||||
{3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"},
|
||||
{4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
|
||||
{5, &IAudioOut::GetReleasedAudioOutBuffer, "GetReleasedAudioOutBuffer"},
|
||||
{5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffer"},
|
||||
{6, nullptr, "ContainsAudioOutBuffer"},
|
||||
{7, nullptr, "AppendAudioOutBufferAuto"},
|
||||
{8, nullptr, "GetReleasedAudioOutBufferAuto"},
|
||||
{7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"},
|
||||
{8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
|
||||
{9, nullptr, "GetAudioOutBufferCount"},
|
||||
{10, nullptr, "GetAudioOutPlayedSampleCount"},
|
||||
{11, nullptr, "FlushAudioOutBuffers"},
|
||||
@@ -96,7 +96,7 @@ private:
|
||||
rb.PushCopyObjects(buffer_event);
|
||||
}
|
||||
|
||||
void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) {
|
||||
void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
@@ -107,7 +107,7 @@ private:
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetReleasedAudioOutBuffer(Kernel::HLERequestContext& ctx) {
|
||||
void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
|
||||
// TODO(st4rk): This is how libtransistor currently implements the
|
||||
@@ -163,7 +163,7 @@ private:
|
||||
AudioState audio_out_state;
|
||||
};
|
||||
|
||||
void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
|
||||
void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
@@ -179,7 +179,7 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(1);
|
||||
}
|
||||
|
||||
void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
|
||||
void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
|
||||
if (!audio_out_interface) {
|
||||
@@ -196,10 +196,10 @@ void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
AudOutU::AudOutU() : ServiceFramework("audout:u") {
|
||||
static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOuts, "ListAudioOuts"},
|
||||
{1, &AudOutU::OpenAudioOut, "OpenAudioOut"},
|
||||
{2, nullptr, "ListAudioOutsAuto"},
|
||||
{3, nullptr, "OpenAudioOutAuto"}};
|
||||
static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"},
|
||||
{1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"},
|
||||
{2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"},
|
||||
{3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ public:
|
||||
private:
|
||||
std::shared_ptr<IAudioOut> audio_out_interface;
|
||||
|
||||
void ListAudioOuts(Kernel::HLERequestContext& ctx);
|
||||
void OpenAudioOut(Kernel::HLERequestContext& ctx);
|
||||
void ListAudioOutsImpl(Kernel::HLERequestContext& ctx);
|
||||
void OpenAudioOutImpl(Kernel::HLERequestContext& ctx);
|
||||
|
||||
enum class PcmFormat : u32 {
|
||||
Invalid = 0,
|
||||
|
||||
@@ -2,221 +2,36 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "boost/container/flat_map.hpp"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_paths.h"
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/filesystem/fsp_srv.h"
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
// Size of emulated sd card free space, reported in bytes.
|
||||
// Just using 32GB because thats reasonable
|
||||
// TODO(DarkLordZach): Eventually make this configurable in settings.
|
||||
constexpr u64 EMULATED_SD_REPORTED_SIZE = 32000000000;
|
||||
|
||||
static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
|
||||
const std::string& dir_name) {
|
||||
if (dir_name == "." || dir_name == "" || dir_name == "/" || dir_name == "\\")
|
||||
return base;
|
||||
|
||||
return base->GetDirectoryRelative(dir_name);
|
||||
}
|
||||
|
||||
VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_)
|
||||
: backing(backing_) {}
|
||||
|
||||
std::string VfsDirectoryServiceWrapper::GetName() const {
|
||||
return backing->GetName();
|
||||
}
|
||||
|
||||
ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path, u64 size) const {
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
|
||||
auto file = dir->CreateFile(FileUtil::GetFilename(path));
|
||||
if (file == nullptr)
|
||||
return ResultCode(-1);
|
||||
if (!file->Resize(size))
|
||||
return ResultCode(-1);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path) const {
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
|
||||
if (!backing->DeleteFile(FileUtil::GetFilename(path)))
|
||||
return ResultCode(-1);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path) const {
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
|
||||
if (dir == nullptr && FileUtil::GetFilename(FileUtil::GetParentPath(path)).empty())
|
||||
dir = backing;
|
||||
auto new_dir = dir->CreateSubdirectory(FileUtil::GetFilename(path));
|
||||
if (new_dir == nullptr)
|
||||
return ResultCode(-1);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path) const {
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
|
||||
if (!dir->DeleteSubdirectory(FileUtil::GetFilename(path)))
|
||||
return ResultCode(-1);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path) const {
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
|
||||
if (!dir->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path)))
|
||||
return ResultCode(-1);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path,
|
||||
const std::string& dest_path) const {
|
||||
auto src = backing->GetFileRelative(src_path);
|
||||
if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) {
|
||||
// Use more-optimized vfs implementation rename.
|
||||
if (src == nullptr)
|
||||
return FileSys::ERROR_PATH_NOT_FOUND;
|
||||
if (!src->Rename(FileUtil::GetFilename(dest_path)))
|
||||
return ResultCode(-1);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Move by hand -- TODO(DarkLordZach): Optimize
|
||||
auto c_res = CreateFile(dest_path, src->GetSize());
|
||||
if (c_res != RESULT_SUCCESS)
|
||||
return c_res;
|
||||
|
||||
auto dest = backing->GetFileRelative(dest_path);
|
||||
ASSERT_MSG(dest != nullptr, "Newly created file with success cannot be found.");
|
||||
|
||||
ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(),
|
||||
"Could not write all of the bytes but everything else has succeded.");
|
||||
|
||||
if (!src->GetContainingDirectory()->DeleteFile(FileUtil::GetFilename(src_path)))
|
||||
return ResultCode(-1);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path,
|
||||
const std::string& dest_path) const {
|
||||
auto src = GetDirectoryRelativeWrapped(backing, src_path);
|
||||
if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) {
|
||||
// Use more-optimized vfs implementation rename.
|
||||
if (src == nullptr)
|
||||
return FileSys::ERROR_PATH_NOT_FOUND;
|
||||
if (!src->Rename(FileUtil::GetFilename(dest_path)))
|
||||
return ResultCode(-1);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// TODO(DarkLordZach): Implement renaming across the tree (move).
|
||||
ASSERT_MSG(false,
|
||||
"Could not rename directory with path \"{}\" to new path \"{}\" because parent dirs "
|
||||
"don't match -- UNIMPLEMENTED",
|
||||
src_path, dest_path);
|
||||
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path,
|
||||
FileSys::Mode mode) const {
|
||||
auto file = backing->GetFileRelative(path);
|
||||
if (file == nullptr)
|
||||
return FileSys::ERROR_PATH_NOT_FOUND;
|
||||
|
||||
if ((static_cast<u32>(mode) & static_cast<u32>(FileSys::Mode::Append)) != 0) {
|
||||
return MakeResult<FileSys::VirtualFile>(
|
||||
std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize()));
|
||||
}
|
||||
|
||||
return MakeResult<FileSys::VirtualFile>(file);
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path) {
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, path);
|
||||
if (dir == nullptr)
|
||||
return ResultCode(-1);
|
||||
return MakeResult(dir);
|
||||
}
|
||||
|
||||
u64 VfsDirectoryServiceWrapper::GetFreeSpaceSize() const {
|
||||
if (backing->IsWritable())
|
||||
return EMULATED_SD_REPORTED_SIZE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
|
||||
const std::string& path) const {
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
|
||||
if (dir == nullptr)
|
||||
return ResultCode(-1);
|
||||
auto filename = FileUtil::GetFilename(path);
|
||||
if (dir->GetFile(filename) != nullptr)
|
||||
return MakeResult(FileSys::EntryType::File);
|
||||
if (dir->GetSubdirectory(filename) != nullptr)
|
||||
return MakeResult(FileSys::EntryType::Directory);
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
// A deferred filesystem for nand save data.
|
||||
// This must be deferred because the directory is dependent on title id, which is not set at
|
||||
// registration time.
|
||||
struct SaveDataDeferredFilesystem : DeferredFilesystem {
|
||||
protected:
|
||||
FileSys::VirtualDir CreateFilesystem() override {
|
||||
u64 title_id = Core::CurrentProcess()->program_id;
|
||||
// TODO(DarkLordZach): Users
|
||||
u32 user_id = 0;
|
||||
std::string nand_directory = fmt::format(
|
||||
"{}save/{:016X}/{:08X}/", FileUtil::GetUserPath(D_NAND_IDX), title_id, user_id);
|
||||
|
||||
auto savedata =
|
||||
std::make_shared<FileSys::RealVfsDirectory>(nand_directory, FileSys::Mode::Write);
|
||||
return savedata;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Map of registered file systems, identified by type. Once an file system is registered here, it
|
||||
* is never removed until UnregisterFileSystems is called.
|
||||
*/
|
||||
static boost::container::flat_map<Type, std::unique_ptr<DeferredFilesystem>> filesystem_map;
|
||||
static FileSys::VirtualFile filesystem_romfs = nullptr;
|
||||
static boost::container::flat_map<Type, std::unique_ptr<FileSys::FileSystemFactory>> filesystem_map;
|
||||
|
||||
ResultCode RegisterFileSystem(std::unique_ptr<DeferredFilesystem>&& factory, Type type) {
|
||||
ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type) {
|
||||
auto result = filesystem_map.emplace(type, std::move(factory));
|
||||
|
||||
bool inserted = result.second;
|
||||
ASSERT_MSG(inserted, "Tried to register more than one system with same id code");
|
||||
|
||||
auto& filesystem = result.first->second;
|
||||
LOG_DEBUG(Service_FS, "Registered file system with id code 0x{:08X}", static_cast<u32>(type));
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode RegisterRomFS(FileSys::VirtualFile filesystem) {
|
||||
ASSERT_MSG(filesystem_romfs == nullptr,
|
||||
"Tried to register more than one system with same id code");
|
||||
|
||||
filesystem_romfs = filesystem;
|
||||
LOG_DEBUG(Service_FS, "Registered file system {} with id code 0x{:08X}", filesystem->GetName(),
|
||||
static_cast<u32>(Type::RomFS));
|
||||
static_cast<u32>(type));
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualDir> OpenFileSystem(Type type) {
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
|
||||
FileSys::Path& path) {
|
||||
LOG_TRACE(Service_FS, "Opening FileSystem with type={}", static_cast<u32>(type));
|
||||
|
||||
auto itr = filesystem_map.find(type);
|
||||
@@ -225,13 +40,7 @@ ResultVal<FileSys::VirtualDir> OpenFileSystem(Type type) {
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return MakeResult(itr->second->Get());
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS() {
|
||||
if (filesystem_romfs == nullptr)
|
||||
return ResultCode(-1);
|
||||
return MakeResult(filesystem_romfs);
|
||||
return itr->second->Open(path);
|
||||
}
|
||||
|
||||
ResultCode FormatFileSystem(Type type) {
|
||||
@@ -243,21 +52,21 @@ ResultCode FormatFileSystem(Type type) {
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return itr->second->Get()->GetParentDirectory()->DeleteSubdirectory(
|
||||
itr->second->Get()->GetName())
|
||||
? RESULT_SUCCESS
|
||||
: ResultCode(-1);
|
||||
FileSys::Path unused;
|
||||
return itr->second->Format(unused);
|
||||
}
|
||||
|
||||
void RegisterFileSystems() {
|
||||
filesystem_map.clear();
|
||||
filesystem_romfs = nullptr;
|
||||
|
||||
std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX);
|
||||
std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX);
|
||||
auto sdcard = std::make_shared<FileSys::RealVfsDirectory>(sd_directory, FileSys::Mode::Write);
|
||||
RegisterFileSystem(std::make_unique<DeferredFilesystem>(sdcard), Type::SDMC);
|
||||
|
||||
RegisterFileSystem(std::make_unique<SaveDataDeferredFilesystem>(), Type::SaveData);
|
||||
auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory));
|
||||
RegisterFileSystem(std::move(savedata), Type::SaveData);
|
||||
|
||||
auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory));
|
||||
RegisterFileSystem(std::move(sdcard), Type::SDMC);
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -31,136 +29,12 @@ enum class Type {
|
||||
SDMC = 3,
|
||||
};
|
||||
|
||||
// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
|
||||
// pointers and booleans. This makes using a VfsDirectory with switch services much easier and
|
||||
// avoids repetitive code.
|
||||
class VfsDirectoryServiceWrapper {
|
||||
public:
|
||||
explicit VfsDirectoryServiceWrapper(FileSys::VirtualDir backing);
|
||||
|
||||
/**
|
||||
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
|
||||
*/
|
||||
std::string GetName() const;
|
||||
|
||||
/**
|
||||
* Create a file specified by its path
|
||||
* @param path Path relative to the Archive
|
||||
* @param size The size of the new file, filled with zeroes
|
||||
* @return Result of the operation
|
||||
*/
|
||||
ResultCode CreateFile(const std::string& path, u64 size) const;
|
||||
|
||||
/**
|
||||
* Delete a file specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
ResultCode DeleteFile(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* Create a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
ResultCode CreateDirectory(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
ResultCode DeleteDirectory(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path and anything under it
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
ResultCode DeleteDirectoryRecursively(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* Rename a File specified by its path
|
||||
* @param src_path Source path relative to the archive
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const;
|
||||
|
||||
/**
|
||||
* Rename a Directory specified by its path
|
||||
* @param src_path Source path relative to the archive
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
ResultCode RenameDirectory(const std::string& src_path, const std::string& dest_path) const;
|
||||
|
||||
/**
|
||||
* Open a file specified by its path, using the specified mode
|
||||
* @param path Path relative to the archive
|
||||
* @param mode Mode to open the file with
|
||||
* @return Opened file, or error code
|
||||
*/
|
||||
ResultVal<FileSys::VirtualFile> OpenFile(const std::string& path, FileSys::Mode mode) const;
|
||||
|
||||
/**
|
||||
* Open a directory specified by its path
|
||||
* @param path Path relative to the archive
|
||||
* @return Opened directory, or error code
|
||||
*/
|
||||
ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path);
|
||||
|
||||
/**
|
||||
* Get the free space
|
||||
* @return The number of free bytes in the archive
|
||||
*/
|
||||
u64 GetFreeSpaceSize() const;
|
||||
|
||||
/**
|
||||
* Get the type of the specified path
|
||||
* @return The type of the specified path or error code
|
||||
*/
|
||||
ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const;
|
||||
|
||||
private:
|
||||
FileSys::VirtualDir backing;
|
||||
};
|
||||
|
||||
// A class that deferres the creation of a filesystem until a later time.
|
||||
// This is useful if construction depends on a variable not known when the filesystem is registered.
|
||||
// Construct this with a filesystem (VirtualDir) to avoid the deferrence feature or override the
|
||||
// CreateFilesystem method which will be called on first use.
|
||||
struct DeferredFilesystem {
|
||||
DeferredFilesystem() = default;
|
||||
|
||||
explicit DeferredFilesystem(FileSys::VirtualDir vfs_directory) : fs(std::move(vfs_directory)) {}
|
||||
|
||||
FileSys::VirtualDir Get() {
|
||||
if (fs == nullptr)
|
||||
fs = CreateFilesystem();
|
||||
|
||||
return fs;
|
||||
}
|
||||
|
||||
virtual ~DeferredFilesystem() = default;
|
||||
|
||||
protected:
|
||||
virtual FileSys::VirtualDir CreateFilesystem() {
|
||||
return fs;
|
||||
}
|
||||
|
||||
private:
|
||||
FileSys::VirtualDir fs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a FileSystem, instances of which can later be opened using its IdCode.
|
||||
* @param factory FileSystem backend interface to use
|
||||
* @param type Type used to access this type of FileSystem
|
||||
*/
|
||||
ResultCode RegisterFileSystem(std::unique_ptr<DeferredFilesystem>&& fs, Type type);
|
||||
|
||||
ResultCode RegisterRomFS(FileSys::VirtualFile fs);
|
||||
ResultCode RegisterFileSystem(std::unique_ptr<FileSys::FileSystemFactory>&& factory, Type type);
|
||||
|
||||
/**
|
||||
* Opens a file system
|
||||
@@ -168,9 +42,8 @@ ResultCode RegisterRomFS(FileSys::VirtualFile fs);
|
||||
* @param path Path to the file system, used with Binary paths
|
||||
* @return FileSys::FileSystemBackend interface to the file system
|
||||
*/
|
||||
ResultVal<FileSys::VirtualDir> OpenFileSystem(Type type);
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS();
|
||||
ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenFileSystem(Type type,
|
||||
FileSys::Path& path);
|
||||
|
||||
/**
|
||||
* Formats a file system
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/storage.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
@@ -19,8 +20,8 @@ namespace Service::FileSystem {
|
||||
|
||||
class IStorage final : public ServiceFramework<IStorage> {
|
||||
public:
|
||||
IStorage(FileSys::VirtualFile backend_)
|
||||
: ServiceFramework("IStorage"), backend(std::move(backend_)) {
|
||||
IStorage(std::unique_ptr<FileSys::StorageBackend>&& backend)
|
||||
: ServiceFramework("IStorage"), backend(std::move(backend)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"},
|
||||
{3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"},
|
||||
@@ -29,7 +30,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
FileSys::VirtualFile backend;
|
||||
std::unique_ptr<FileSys::StorageBackend> backend;
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
@@ -51,8 +52,8 @@ private:
|
||||
}
|
||||
|
||||
// Read the data from the Storage backend
|
||||
std::vector<u8> output = backend->ReadBytes(length, offset);
|
||||
auto res = MakeResult<size_t>(output.size());
|
||||
std::vector<u8> output(length);
|
||||
ResultVal<size_t> res = backend->Read(offset, length, output.data());
|
||||
if (res.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res.Code());
|
||||
@@ -69,8 +70,8 @@ private:
|
||||
|
||||
class IFile final : public ServiceFramework<IFile> {
|
||||
public:
|
||||
explicit IFile(FileSys::VirtualFile backend_)
|
||||
: ServiceFramework("IFile"), backend(std::move(backend_)) {
|
||||
explicit IFile(std::unique_ptr<FileSys::StorageBackend>&& backend)
|
||||
: ServiceFramework("IFile"), backend(std::move(backend)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"},
|
||||
{2, &IFile::Flush, "Flush"}, {3, &IFile::SetSize, "SetSize"},
|
||||
@@ -80,7 +81,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
FileSys::VirtualFile backend;
|
||||
std::unique_ptr<FileSys::StorageBackend> backend;
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
@@ -103,8 +104,8 @@ private:
|
||||
}
|
||||
|
||||
// Read the data from the Storage backend
|
||||
std::vector<u8> output = backend->ReadBytes(length, offset);
|
||||
auto res = MakeResult<size_t>(output.size());
|
||||
std::vector<u8> output(length);
|
||||
ResultVal<size_t> res = backend->Read(offset, length, output.data());
|
||||
if (res.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res.Code());
|
||||
@@ -139,10 +140,9 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> data = ctx.ReadBuffer();
|
||||
data.resize(length);
|
||||
// Write the data to the Storage backend
|
||||
auto res = MakeResult<size_t>(backend->WriteBytes(data, offset));
|
||||
std::vector<u8> data = ctx.ReadBuffer();
|
||||
ResultVal<size_t> res = backend->Write(offset, length, true, data.data());
|
||||
if (res.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res.Code());
|
||||
@@ -155,8 +155,7 @@ private:
|
||||
|
||||
void Flush(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
// Exists for SDK compatibiltity -- No need to flush file.
|
||||
backend->Flush();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -165,7 +164,7 @@ private:
|
||||
void SetSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 size = rp.Pop<u64>();
|
||||
backend->Resize(size);
|
||||
backend->SetSize(size);
|
||||
LOG_DEBUG(Service_FS, "called, size={}", size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
@@ -182,38 +181,19 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vector<T>& new_data,
|
||||
FileSys::EntryType type) {
|
||||
for (const auto& new_entry : new_data) {
|
||||
FileSys::Entry entry;
|
||||
entry.filename[0] = '\0';
|
||||
std::strncat(entry.filename, new_entry->GetName().c_str(), FileSys::FILENAME_LENGTH - 1);
|
||||
entry.type = type;
|
||||
entry.file_size = new_entry->GetSize();
|
||||
entries.emplace_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
class IDirectory final : public ServiceFramework<IDirectory> {
|
||||
public:
|
||||
explicit IDirectory(FileSys::VirtualDir backend_)
|
||||
: ServiceFramework("IDirectory"), backend(std::move(backend_)) {
|
||||
explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend)
|
||||
: ServiceFramework("IDirectory"), backend(std::move(backend)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IDirectory::Read, "Read"},
|
||||
{1, &IDirectory::GetEntryCount, "GetEntryCount"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
// Build entry index now to save time later.
|
||||
BuildEntryIndex(entries, backend->GetFiles(), FileSys::File);
|
||||
BuildEntryIndex(entries, backend->GetSubdirectories(), FileSys::Directory);
|
||||
}
|
||||
|
||||
private:
|
||||
FileSys::VirtualDir backend;
|
||||
std::vector<FileSys::Entry> entries;
|
||||
u64 next_entry_index = 0;
|
||||
std::unique_ptr<FileSys::DirectoryBackend> backend;
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
@@ -224,31 +204,26 @@ private:
|
||||
// Calculate how many entries we can fit in the output buffer
|
||||
u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
|
||||
|
||||
// Cap at total number of entries.
|
||||
u64 actual_entries = std::min(count_entries, entries.size() - next_entry_index);
|
||||
|
||||
// Read the data from the Directory backend
|
||||
std::vector<FileSys::Entry> entry_data(entries.begin() + next_entry_index,
|
||||
entries.begin() + next_entry_index + actual_entries);
|
||||
|
||||
next_entry_index += actual_entries;
|
||||
std::vector<FileSys::Entry> entries(count_entries);
|
||||
u64 read_entries = backend->Read(count_entries, entries.data());
|
||||
|
||||
// Convert the data into a byte array
|
||||
std::vector<u8> output(entry_data.size() * sizeof(FileSys::Entry));
|
||||
std::memcpy(output.data(), entry_data.data(), output.size());
|
||||
std::vector<u8> output(entries.size() * sizeof(FileSys::Entry));
|
||||
std::memcpy(output.data(), entries.data(), output.size());
|
||||
|
||||
// Write the data to memory
|
||||
ctx.WriteBuffer(output);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(actual_entries);
|
||||
rb.Push(read_entries);
|
||||
}
|
||||
|
||||
void GetEntryCount(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
u64 count = entries.size() - next_entry_index;
|
||||
u64 count = backend->GetEntryCount();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -258,7 +233,7 @@ private:
|
||||
|
||||
class IFileSystem final : public ServiceFramework<IFileSystem> {
|
||||
public:
|
||||
explicit IFileSystem(FileSys::VirtualDir backend)
|
||||
explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend)
|
||||
: ServiceFramework("IFileSystem"), backend(std::move(backend)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IFileSystem::CreateFile, "CreateFile"},
|
||||
@@ -293,7 +268,7 @@ public:
|
||||
LOG_DEBUG(Service_FS, "called file {} mode 0x{:X} size 0x{:08X}", name, mode, size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(backend.CreateFile(name, size));
|
||||
rb.Push(backend->CreateFile(name, size));
|
||||
}
|
||||
|
||||
void DeleteFile(Kernel::HLERequestContext& ctx) {
|
||||
@@ -305,7 +280,7 @@ public:
|
||||
LOG_DEBUG(Service_FS, "called file {}", name);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(backend.DeleteFile(name));
|
||||
rb.Push(backend->DeleteFile(name));
|
||||
}
|
||||
|
||||
void CreateDirectory(Kernel::HLERequestContext& ctx) {
|
||||
@@ -317,7 +292,7 @@ public:
|
||||
LOG_DEBUG(Service_FS, "called directory {}", name);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(backend.CreateDirectory(name));
|
||||
rb.Push(backend->CreateDirectory(name));
|
||||
}
|
||||
|
||||
void RenameFile(Kernel::HLERequestContext& ctx) {
|
||||
@@ -335,7 +310,7 @@ public:
|
||||
LOG_DEBUG(Service_FS, "called file '{}' to file '{}'", src_name, dst_name);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(backend.RenameFile(src_name, dst_name));
|
||||
rb.Push(backend->RenameFile(src_name, dst_name));
|
||||
}
|
||||
|
||||
void OpenFile(Kernel::HLERequestContext& ctx) {
|
||||
@@ -348,14 +323,14 @@ public:
|
||||
|
||||
LOG_DEBUG(Service_FS, "called file {} mode {}", name, static_cast<u32>(mode));
|
||||
|
||||
auto result = backend.OpenFile(name, mode);
|
||||
auto result = backend->OpenFile(name, mode);
|
||||
if (result.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
IFile file(result.Unwrap());
|
||||
auto file = std::move(result.Unwrap());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -373,14 +348,14 @@ public:
|
||||
|
||||
LOG_DEBUG(Service_FS, "called directory {} filter {}", name, filter_flags);
|
||||
|
||||
auto result = backend.OpenDirectory(name);
|
||||
auto result = backend->OpenDirectory(name);
|
||||
if (result.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
IDirectory directory(result.Unwrap());
|
||||
auto directory = std::move(result.Unwrap());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -395,7 +370,7 @@ public:
|
||||
|
||||
LOG_DEBUG(Service_FS, "called file {}", name);
|
||||
|
||||
auto result = backend.GetEntryType(name);
|
||||
auto result = backend->GetEntryType(name);
|
||||
if (result.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result.Code());
|
||||
@@ -415,7 +390,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
VfsDirectoryServiceWrapper backend;
|
||||
std::unique_ptr<FileSys::FileSystemBackend> backend;
|
||||
};
|
||||
|
||||
FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
@@ -516,7 +491,8 @@ void FSP_SRV::TryLoadRomFS() {
|
||||
if (romfs) {
|
||||
return;
|
||||
}
|
||||
auto res = OpenRomFS();
|
||||
FileSys::Path unused;
|
||||
auto res = OpenFileSystem(Type::RomFS, unused);
|
||||
if (res.Succeeded()) {
|
||||
romfs = std::move(res.Unwrap());
|
||||
}
|
||||
@@ -532,7 +508,8 @@ void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IFileSystem filesystem(OpenFileSystem(Type::SDMC).Unwrap());
|
||||
FileSys::Path unused;
|
||||
auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -555,11 +532,20 @@ void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) {
|
||||
void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
|
||||
IFileSystem filesystem(OpenFileSystem(Type::SaveData).Unwrap());
|
||||
// TODO(Subv): Read the input parameters and mount the requested savedata instead of always
|
||||
// mounting the current process' savedata.
|
||||
FileSys::Path unused;
|
||||
auto filesystem = OpenFileSystem(Type::SaveData, unused);
|
||||
|
||||
if (filesystem.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
|
||||
rb.Push(ResultCode(ErrorModule::FS, FileSys::ErrCodes::SaveDataNotFound));
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
|
||||
rb.PushIpcInterface<IFileSystem>(std::move(filesystem.Unwrap()));
|
||||
}
|
||||
|
||||
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
@@ -582,11 +568,18 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
IStorage storage(romfs);
|
||||
// Attempt to open a StorageBackend interface to the RomFS
|
||||
auto storage = romfs->OpenFile({}, {});
|
||||
if (storage.Failed()) {
|
||||
LOG_CRITICAL(Service_FS, "no storage interface available!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(storage.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IStorage>(std::move(storage));
|
||||
rb.PushIpcInterface<IStorage>(std::move(storage.Unwrap()));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -29,7 +29,7 @@ private:
|
||||
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
void OpenRomStorage(Kernel::HLERequestContext& ctx);
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
std::unique_ptr<FileSys::FileSystemBackend> romfs;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -75,7 +75,7 @@ private:
|
||||
|
||||
// Set up controllers as neon red+blue Joy-Con attached to console
|
||||
ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header;
|
||||
controller_header.type = ControllerType_Handheld | ControllerType_JoyconPair;
|
||||
controller_header.type = ControllerType_Handheld;
|
||||
controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent;
|
||||
controller_header.right_color_body = JOYCON_BODY_NEON_RED;
|
||||
controller_header.right_color_buttons = JOYCON_BUTTONS_NEON_RED;
|
||||
@@ -84,23 +84,21 @@ private:
|
||||
|
||||
for (size_t controller = 0; controller < mem.controllers.size(); controller++) {
|
||||
for (int index = 0; index < HID_NUM_LAYOUTS; index++) {
|
||||
// TODO(DarkLordZach): Is this layout/controller config actually invalid?
|
||||
if (controller == Controller_Handheld && index == Layout_Single)
|
||||
continue;
|
||||
|
||||
ControllerLayout& layout = mem.controllers[controller].layouts[index];
|
||||
layout.header.num_entries = HID_NUM_ENTRIES;
|
||||
layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
|
||||
|
||||
// HID shared memory stores the state of the past 17 samples in a circlular buffer,
|
||||
// each with a timestamp in number of samples since boot.
|
||||
const ControllerInputEntry& last_entry = layout.entries[layout.header.latest_entry];
|
||||
|
||||
layout.header.timestamp_ticks = CoreTiming::GetTicks();
|
||||
layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
|
||||
|
||||
ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
|
||||
entry.timestamp++;
|
||||
entry.timestamp = last_entry.timestamp + 1;
|
||||
// TODO(shinyquagsire23): Is this always identical to timestamp?
|
||||
entry.timestamp_2++;
|
||||
entry.timestamp_2 = entry.timestamp;
|
||||
|
||||
// TODO(shinyquagsire23): More than just handheld input
|
||||
if (controller != Controller_Handheld)
|
||||
|
||||
@@ -148,6 +148,24 @@ private:
|
||||
|
||||
LOG_DEBUG(Service_NIFM, "called");
|
||||
}
|
||||
void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
}
|
||||
void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
}
|
||||
void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(0);
|
||||
}
|
||||
};
|
||||
|
||||
IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
|
||||
@@ -167,11 +185,11 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
|
||||
{14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
|
||||
{15, nullptr, "GetCurrentIpConfigInfo"},
|
||||
{16, nullptr, "SetWirelessCommunicationEnabled"},
|
||||
{17, nullptr, "IsWirelessCommunicationEnabled"},
|
||||
{17, &IGeneralService::IsWirelessCommunicationEnabled, "IsWirelessCommunicationEnabled"},
|
||||
{18, nullptr, "GetInternetConnectionStatus"},
|
||||
{19, nullptr, "SetEthernetCommunicationEnabled"},
|
||||
{20, nullptr, "IsEthernetCommunicationEnabled"},
|
||||
{21, nullptr, "IsAnyInternetRequestAccepted"},
|
||||
{20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
|
||||
{21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
|
||||
{22, nullptr, "IsAnyForegroundRequestAccepted"},
|
||||
{23, nullptr, "PutToSleep"},
|
||||
{24, nullptr, "WakeUp"},
|
||||
|
||||
@@ -29,24 +29,9 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<
|
||||
u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IocGetConfigParams params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||
LOG_DEBUG(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(),
|
||||
LOG_TRACE(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(),
|
||||
params.param_str.data());
|
||||
|
||||
if (!strcmp(params.domain_str.data(), "nv")) {
|
||||
if (!strcmp(params.param_str.data(), "NV_MEMORY_PROFILER")) {
|
||||
params.config_str[0] = '0';
|
||||
} else if (!strcmp(params.param_str.data(), "NVN_THROUGH_OPENGL")) {
|
||||
params.config_str[0] = '0';
|
||||
} else if (!strcmp(params.param_str.data(), "NVRM_GPU_PREVENT_USE")) {
|
||||
params.config_str[0] = '0';
|
||||
} else {
|
||||
params.config_str[0] = '\0';
|
||||
}
|
||||
} else {
|
||||
UNIMPLEMENTED(); // unknown domain? Only nv has been seen so far on hardware
|
||||
}
|
||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||
return 0;
|
||||
return 0x30006; // Returns error on production mode
|
||||
}
|
||||
|
||||
u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
#include <cinttypes>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
@@ -45,11 +47,55 @@ static std::string FindRomFS(const std::string& directory) {
|
||||
return filepath_romfs;
|
||||
}
|
||||
|
||||
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file)
|
||||
: AppLoader(std::move(file)) {}
|
||||
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file,
|
||||
std::string filepath)
|
||||
: AppLoader(std::move(file)), filepath(std::move(filepath)) {}
|
||||
|
||||
FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
if (FileSys::IsDirectoryExeFS(file->GetContainingDirectory())) {
|
||||
FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& file,
|
||||
const std::string& filepath) {
|
||||
bool is_main_found{};
|
||||
bool is_npdm_found{};
|
||||
bool is_rtld_found{};
|
||||
bool is_sdk_found{};
|
||||
|
||||
const auto callback = [&](unsigned* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
// Skip directories
|
||||
std::string physical_name = directory + virtual_name;
|
||||
if (FileUtil::IsDirectory(physical_name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify filename
|
||||
if (Common::ToLower(virtual_name) == "main") {
|
||||
is_main_found = true;
|
||||
} else if (Common::ToLower(virtual_name) == "main.npdm") {
|
||||
is_npdm_found = true;
|
||||
return true;
|
||||
} else if (Common::ToLower(virtual_name) == "rtld") {
|
||||
is_rtld_found = true;
|
||||
} else if (Common::ToLower(virtual_name) == "sdk") {
|
||||
is_sdk_found = true;
|
||||
} else {
|
||||
// Continue searching
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify file is an NSO
|
||||
FileUtil::IOFile file(physical_name, "rb");
|
||||
if (AppLoader_NSO::IdentifyType(file, physical_name) != FileType::NSO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are done if we've found and verified all required NSOs
|
||||
return !(is_main_found && is_npdm_found && is_rtld_found && is_sdk_found);
|
||||
};
|
||||
|
||||
// Search the directory recursively, looking for the required modules
|
||||
const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP;
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, directory, callback);
|
||||
|
||||
if (is_main_found && is_npdm_found && is_rtld_found && is_sdk_found) {
|
||||
return FileType::DeconstructedRomDirectory;
|
||||
}
|
||||
|
||||
@@ -61,13 +107,14 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
if (is_loaded) {
|
||||
return ResultStatus::ErrorAlreadyLoaded;
|
||||
}
|
||||
if (!file.IsOpen()) {
|
||||
return ResultStatus::Error;
|
||||
}
|
||||
|
||||
const FileSys::VirtualDir dir = file->GetContainingDirectory();
|
||||
const FileSys::VirtualFile npdm = dir->GetFile("main.npdm");
|
||||
if (npdm == nullptr)
|
||||
return ResultStatus::ErrorInvalidFormat;
|
||||
const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP;
|
||||
const std::string npdm_path = directory + DIR_SEP + "main.npdm";
|
||||
|
||||
ResultStatus result = metadata.Load(npdm);
|
||||
ResultStatus result = metadata.Load(npdm_path);
|
||||
if (result != ResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
@@ -82,12 +129,13 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR};
|
||||
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
|
||||
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
|
||||
const std::string path = directory + DIR_SEP + module;
|
||||
const VAddr load_addr = next_load_addr;
|
||||
const FileSys::VirtualFile module_file = dir->GetFile(module);
|
||||
if (module_file != nullptr)
|
||||
next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr);
|
||||
next_load_addr = AppLoader_NSO::LoadModule(path, load_addr);
|
||||
if (next_load_addr) {
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
|
||||
} else {
|
||||
next_load_addr = load_addr;
|
||||
}
|
||||
@@ -102,20 +150,42 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
metadata.GetMainThreadStackSize());
|
||||
|
||||
// Find the RomFS by searching for a ".romfs" file in this directory
|
||||
const auto& files = dir->GetFiles();
|
||||
const auto romfs_iter =
|
||||
std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
|
||||
return file->GetName().find(".romfs") != std::string::npos;
|
||||
});
|
||||
filepath_romfs = FindRomFS(directory);
|
||||
|
||||
// TODO(DarkLordZach): Identify RomFS if its a subdirectory.
|
||||
const auto romfs = (romfs_iter == files.end()) ? nullptr : *romfs_iter;
|
||||
|
||||
if (romfs != nullptr)
|
||||
Service::FileSystem::RegisterRomFS(romfs);
|
||||
// Register the RomFS if a ".romfs" file was found
|
||||
if (!filepath_romfs.empty()) {
|
||||
Service::FileSystem::RegisterFileSystem(std::make_unique<FileSys::RomFS_Factory>(*this),
|
||||
Service::FileSystem::Type::RomFS);
|
||||
}
|
||||
|
||||
is_loaded = true;
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(
|
||||
std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) {
|
||||
|
||||
if (filepath_romfs.empty()) {
|
||||
LOG_DEBUG(Loader, "No RomFS available");
|
||||
return ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
// We reopen the file, to allow its position to be independent
|
||||
romfs_file = std::make_shared<FileUtil::IOFile>(filepath_romfs, "rb");
|
||||
if (!romfs_file->IsOpen()) {
|
||||
return ResultStatus::Error;
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
size = romfs_file->GetSize();
|
||||
|
||||
LOG_DEBUG(Loader, "RomFS offset: 0x{:016X}", offset);
|
||||
LOG_DEBUG(Loader, "RomFS size: 0x{:016X}", size);
|
||||
|
||||
// Reset read pointer
|
||||
file.Seek(0, SEEK_SET);
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -20,22 +20,28 @@ namespace Loader {
|
||||
*/
|
||||
class AppLoader_DeconstructedRomDirectory final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file);
|
||||
AppLoader_DeconstructedRomDirectory(FileUtil::IOFile&& file, std::string filepath);
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
* @param file std::shared_ptr<VfsFile> open file
|
||||
* @param file FileUtil::IOFile open file
|
||||
* @param filepath Path of the file that we are opening.
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
|
||||
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
return IdentifyType(file, filepath);
|
||||
}
|
||||
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
|
||||
|
||||
ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
|
||||
u64& size) override;
|
||||
|
||||
private:
|
||||
std::string filepath_romfs;
|
||||
std::string filepath;
|
||||
FileSys::ProgramMetadata metadata;
|
||||
};
|
||||
|
||||
|
||||
@@ -365,17 +365,20 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const
|
||||
|
||||
namespace Loader {
|
||||
|
||||
AppLoader_ELF::AppLoader_ELF(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
|
||||
AppLoader_ELF::AppLoader_ELF(FileUtil::IOFile&& file, std::string filename)
|
||||
: AppLoader(std::move(file)), filename(std::move(filename)) {}
|
||||
|
||||
FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file, const std::string&) {
|
||||
static constexpr u16 ELF_MACHINE_ARM{0x28};
|
||||
|
||||
u32 magic = 0;
|
||||
if (4 != file->ReadObject(&magic))
|
||||
file.Seek(0, SEEK_SET);
|
||||
if (1 != file.ReadArray<u32>(&magic, 1))
|
||||
return FileType::Error;
|
||||
|
||||
u16 machine = 0;
|
||||
if (2 != file->ReadObject(&machine, 18))
|
||||
file.Seek(18, SEEK_SET);
|
||||
if (1 != file.ReadArray<u16>(&machine, 1))
|
||||
return FileType::Error;
|
||||
|
||||
if (Common::MakeMagic('\x7f', 'E', 'L', 'F') == magic && ELF_MACHINE_ARM == machine)
|
||||
@@ -388,13 +391,20 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
if (is_loaded)
|
||||
return ResultStatus::ErrorAlreadyLoaded;
|
||||
|
||||
std::vector<u8> buffer = file->ReadAllBytes();
|
||||
if (buffer.size() != file->GetSize())
|
||||
if (!file.IsOpen())
|
||||
return ResultStatus::Error;
|
||||
|
||||
// Reset read pointer in case this file has been read before.
|
||||
file.Seek(0, SEEK_SET);
|
||||
|
||||
size_t size = file.GetSize();
|
||||
std::unique_ptr<u8[]> buffer(new u8[size]);
|
||||
if (file.ReadBytes(&buffer[0], size) != size)
|
||||
return ResultStatus::Error;
|
||||
|
||||
ElfReader elf_reader(&buffer[0]);
|
||||
SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR);
|
||||
codeset->name = file->GetName();
|
||||
codeset->name = filename;
|
||||
|
||||
process->LoadModule(codeset, codeset->entrypoint);
|
||||
process->svc_access_mask.set();
|
||||
|
||||
@@ -16,20 +16,24 @@ namespace Loader {
|
||||
/// Loads an ELF/AXF file
|
||||
class AppLoader_ELF final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_ELF(FileSys::VirtualFile file);
|
||||
AppLoader_ELF(FileUtil::IOFile&& file, std::string filename);
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
* @param file std::shared_ptr<VfsFile> open file
|
||||
* @param file FileUtil::IOFile open file
|
||||
* @param filepath Path of the file that we are opening.
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
|
||||
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
return IdentifyType(file, filename);
|
||||
}
|
||||
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
|
||||
|
||||
private:
|
||||
std::string filename;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <string>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/loader/deconstructed_rom_directory.h"
|
||||
#include "core/loader/elf.h"
|
||||
@@ -22,11 +21,11 @@ const std::initializer_list<Kernel::AddressMapping> default_address_mappings = {
|
||||
{0x1F000000, 0x600000, false}, // entire VRAM
|
||||
};
|
||||
|
||||
FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) {
|
||||
FileType type;
|
||||
|
||||
#define CHECK_TYPE(loader) \
|
||||
type = AppLoader_##loader::IdentifyType(file); \
|
||||
type = AppLoader_##loader::IdentifyType(file, filepath); \
|
||||
if (FileType::Error != type) \
|
||||
return type;
|
||||
|
||||
@@ -42,22 +41,25 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
}
|
||||
|
||||
FileType IdentifyFile(const std::string& file_name) {
|
||||
return IdentifyFile(FileSys::VirtualFile(std::make_shared<FileSys::RealVfsFile>(file_name)));
|
||||
FileUtil::IOFile file(file_name, "rb");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Loader, "Failed to load file {}", file_name);
|
||||
return FileType::Unknown;
|
||||
}
|
||||
|
||||
return IdentifyFile(file, file_name);
|
||||
}
|
||||
|
||||
FileType GuessFromFilename(const std::string& name) {
|
||||
if (name == "main")
|
||||
return FileType::DeconstructedRomDirectory;
|
||||
FileType GuessFromExtension(const std::string& extension_) {
|
||||
std::string extension = Common::ToLower(extension_);
|
||||
|
||||
const std::string extension = Common::ToLower(FileUtil::GetExtensionFromFilename(name));
|
||||
|
||||
if (extension == "elf")
|
||||
if (extension == ".elf")
|
||||
return FileType::ELF;
|
||||
if (extension == "nro")
|
||||
else if (extension == ".nro")
|
||||
return FileType::NRO;
|
||||
if (extension == "nso")
|
||||
else if (extension == ".nso")
|
||||
return FileType::NSO;
|
||||
if (extension == "nca")
|
||||
else if (extension == ".nca")
|
||||
return FileType::NCA;
|
||||
|
||||
return FileType::Unknown;
|
||||
@@ -91,47 +93,58 @@ const char* GetFileTypeString(FileType type) {
|
||||
* @param filepath the file full path (with name)
|
||||
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
|
||||
*/
|
||||
static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileType type) {
|
||||
static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileType type,
|
||||
const std::string& filename,
|
||||
const std::string& filepath) {
|
||||
switch (type) {
|
||||
|
||||
// Standard ELF file format.
|
||||
case FileType::ELF:
|
||||
return std::make_unique<AppLoader_ELF>(std::move(file));
|
||||
return std::make_unique<AppLoader_ELF>(std::move(file), filename);
|
||||
|
||||
// NX NSO file format.
|
||||
case FileType::NSO:
|
||||
return std::make_unique<AppLoader_NSO>(std::move(file));
|
||||
return std::make_unique<AppLoader_NSO>(std::move(file), filepath);
|
||||
|
||||
// NX NRO file format.
|
||||
case FileType::NRO:
|
||||
return std::make_unique<AppLoader_NRO>(std::move(file));
|
||||
return std::make_unique<AppLoader_NRO>(std::move(file), filepath);
|
||||
|
||||
// NX NCA file format.
|
||||
case FileType::NCA:
|
||||
return std::make_unique<AppLoader_NCA>(std::move(file));
|
||||
return std::make_unique<AppLoader_NCA>(std::move(file), filepath);
|
||||
|
||||
// NX deconstructed ROM directory.
|
||||
case FileType::DeconstructedRomDirectory:
|
||||
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
|
||||
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file), filepath);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) {
|
||||
FileType type = IdentifyFile(file);
|
||||
FileType filename_type = GuessFromFilename(file->GetName());
|
||||
std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
|
||||
FileUtil::IOFile file(filename, "rb");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Loader, "Failed to load file {}", filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string filename_filename, filename_extension;
|
||||
Common::SplitPath(filename, nullptr, &filename_filename, &filename_extension);
|
||||
|
||||
FileType type = IdentifyFile(file, filename);
|
||||
FileType filename_type = GuessFromExtension(filename_extension);
|
||||
|
||||
if (type != filename_type) {
|
||||
LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName());
|
||||
LOG_WARNING(Loader, "File {} has a different type than its extension.", filename);
|
||||
if (FileType::Unknown == type)
|
||||
type = filename_type;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type));
|
||||
LOG_DEBUG(Loader, "Loading file {} as {}...", filename, GetFileTypeString(type));
|
||||
|
||||
return GetFileLoader(std::move(file), type);
|
||||
return GetFileLoader(std::move(file), type, filename_filename, filename);
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -37,9 +36,10 @@ enum class FileType {
|
||||
/**
|
||||
* Identifies the type of a bootable file based on the magic value in its header.
|
||||
* @param file open file
|
||||
* @param filepath Path of the file that we are opening.
|
||||
* @return FileType of file
|
||||
*/
|
||||
FileType IdentifyFile(FileSys::VirtualFile file);
|
||||
FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath);
|
||||
|
||||
/**
|
||||
* Identifies the type of a bootable file based on the magic value in its header.
|
||||
@@ -50,12 +50,12 @@ FileType IdentifyFile(FileSys::VirtualFile file);
|
||||
FileType IdentifyFile(const std::string& file_name);
|
||||
|
||||
/**
|
||||
* Guess the type of a bootable file from its name
|
||||
* @param name String name of bootable file
|
||||
* Guess the type of a bootable file from its extension
|
||||
* @param extension String extension of bootable file
|
||||
* @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine
|
||||
* a filetype, and will never return FileType::Error.
|
||||
*/
|
||||
FileType GuessFromFilename(const std::string& name);
|
||||
FileType GuessFromExtension(const std::string& extension);
|
||||
|
||||
/**
|
||||
* Convert a FileType into a string which can be displayed to the user.
|
||||
@@ -79,7 +79,7 @@ enum class ResultStatus {
|
||||
/// Interface for loading an application
|
||||
class AppLoader : NonCopyable {
|
||||
public:
|
||||
AppLoader(FileSys::VirtualFile file) : file(std::move(file)) {}
|
||||
AppLoader(FileUtil::IOFile&& file) : file(std::move(file)) {}
|
||||
virtual ~AppLoader() {}
|
||||
|
||||
/**
|
||||
@@ -154,20 +154,26 @@ public:
|
||||
/**
|
||||
* Get the RomFS of the application
|
||||
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
|
||||
* @param file The file containing the RomFS
|
||||
* @param romfs_file The file containing the RomFS
|
||||
* @param offset The offset the romfs begins on
|
||||
* @param size The size of the romfs
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
virtual ResultStatus ReadRomFS(FileSys::VirtualFile& dir) {
|
||||
virtual ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
|
||||
u64& size) {
|
||||
return ResultStatus::ErrorNotImplemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the update RomFS of the application
|
||||
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
|
||||
* @param file The file containing the RomFS
|
||||
* @param romfs_file The file containing the RomFS
|
||||
* @param offset The offset the romfs begins on
|
||||
* @param size The size of the romfs
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
virtual ResultStatus ReadUpdateRomFS(FileSys::VirtualFile& file) {
|
||||
virtual ResultStatus ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
|
||||
u64& size) {
|
||||
return ResultStatus::ErrorNotImplemented;
|
||||
}
|
||||
|
||||
@@ -181,7 +187,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
FileSys::VirtualFile file;
|
||||
FileUtil::IOFile file;
|
||||
bool is_loaded = false;
|
||||
};
|
||||
|
||||
@@ -196,6 +202,6 @@ extern const std::initializer_list<Kernel::AddressMapping> default_address_mappi
|
||||
* @param filename String filename of bootable file
|
||||
* @return best loader for this file
|
||||
*/
|
||||
std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file);
|
||||
std::unique_ptr<AppLoader> GetLoader(const std::string& filename);
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -4,11 +4,15 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
@@ -18,15 +22,208 @@
|
||||
|
||||
namespace Loader {
|
||||
|
||||
AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(file) {}
|
||||
// Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
|
||||
constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
|
||||
|
||||
FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
// TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support.
|
||||
FileSys::NCAHeader header{};
|
||||
if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header))
|
||||
constexpr u64 SECTION_HEADER_SIZE = 0x200;
|
||||
constexpr u64 SECTION_HEADER_OFFSET = 0x400;
|
||||
|
||||
enum class NcaContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 };
|
||||
|
||||
enum class NcaSectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 };
|
||||
|
||||
struct NcaSectionTableEntry {
|
||||
u32_le media_offset;
|
||||
u32_le media_end_offset;
|
||||
INSERT_PADDING_BYTES(0x8);
|
||||
};
|
||||
static_assert(sizeof(NcaSectionTableEntry) == 0x10, "NcaSectionTableEntry has incorrect size.");
|
||||
|
||||
struct NcaHeader {
|
||||
std::array<u8, 0x100> rsa_signature_1;
|
||||
std::array<u8, 0x100> rsa_signature_2;
|
||||
u32_le magic;
|
||||
u8 is_system;
|
||||
NcaContentType content_type;
|
||||
u8 crypto_type;
|
||||
u8 key_index;
|
||||
u64_le size;
|
||||
u64_le title_id;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
u32_le sdk_version;
|
||||
u8 crypto_type_2;
|
||||
INSERT_PADDING_BYTES(15);
|
||||
std::array<u8, 0x10> rights_id;
|
||||
std::array<NcaSectionTableEntry, 0x4> section_tables;
|
||||
std::array<std::array<u8, 0x20>, 0x4> hash_tables;
|
||||
std::array<std::array<u8, 0x10>, 0x4> key_area;
|
||||
INSERT_PADDING_BYTES(0xC0);
|
||||
};
|
||||
static_assert(sizeof(NcaHeader) == 0x400, "NcaHeader has incorrect size.");
|
||||
|
||||
struct NcaSectionHeaderBlock {
|
||||
INSERT_PADDING_BYTES(3);
|
||||
NcaSectionFilesystemType filesystem_type;
|
||||
u8 crypto_type;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(NcaSectionHeaderBlock) == 0x8, "NcaSectionHeaderBlock has incorrect size.");
|
||||
|
||||
struct Pfs0Superblock {
|
||||
NcaSectionHeaderBlock header_block;
|
||||
std::array<u8, 0x20> hash;
|
||||
u32_le size;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u64_le hash_table_offset;
|
||||
u64_le hash_table_size;
|
||||
u64_le pfs0_header_offset;
|
||||
u64_le pfs0_size;
|
||||
INSERT_PADDING_BYTES(432);
|
||||
};
|
||||
static_assert(sizeof(Pfs0Superblock) == 0x200, "Pfs0Superblock has incorrect size.");
|
||||
|
||||
static bool IsValidNca(const NcaHeader& header) {
|
||||
return header.magic == Common::MakeMagic('N', 'C', 'A', '2') ||
|
||||
header.magic == Common::MakeMagic('N', 'C', 'A', '3');
|
||||
}
|
||||
|
||||
// TODO(DarkLordZach): Add support for encrypted.
|
||||
class Nca final {
|
||||
std::vector<FileSys::PartitionFilesystem> pfs;
|
||||
std::vector<u64> pfs_offset;
|
||||
|
||||
u64 romfs_offset = 0;
|
||||
u64 romfs_size = 0;
|
||||
|
||||
boost::optional<u8> exefs_id = boost::none;
|
||||
|
||||
FileUtil::IOFile file;
|
||||
std::string path;
|
||||
|
||||
u64 GetExeFsFileOffset(const std::string& file_name) const;
|
||||
u64 GetExeFsFileSize(const std::string& file_name) const;
|
||||
|
||||
public:
|
||||
ResultStatus Load(FileUtil::IOFile&& file, std::string path);
|
||||
|
||||
FileSys::PartitionFilesystem GetPfs(u8 id) const;
|
||||
|
||||
u64 GetRomFsOffset() const;
|
||||
u64 GetRomFsSize() const;
|
||||
|
||||
std::vector<u8> GetExeFsFile(const std::string& file_name);
|
||||
};
|
||||
|
||||
static bool IsPfsExeFs(const FileSys::PartitionFilesystem& pfs) {
|
||||
// According to switchbrew, an exefs must only contain these two files:
|
||||
return pfs.GetFileSize("main") > 0 && pfs.GetFileSize("main.npdm") > 0;
|
||||
}
|
||||
|
||||
ResultStatus Nca::Load(FileUtil::IOFile&& in_file, std::string in_path) {
|
||||
file = std::move(in_file);
|
||||
path = in_path;
|
||||
file.Seek(0, SEEK_SET);
|
||||
std::array<u8, sizeof(NcaHeader)> header_array{};
|
||||
if (sizeof(NcaHeader) != file.ReadBytes(header_array.data(), sizeof(NcaHeader)))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
NcaHeader header{};
|
||||
std::memcpy(&header, header_array.data(), sizeof(NcaHeader));
|
||||
if (!IsValidNca(header))
|
||||
return ResultStatus::ErrorInvalidFormat;
|
||||
|
||||
int number_sections =
|
||||
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
|
||||
[](NcaSectionTableEntry entry) { return entry.media_offset > 0; });
|
||||
|
||||
for (int i = 0; i < number_sections; ++i) {
|
||||
// Seek to beginning of this section.
|
||||
file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET);
|
||||
std::array<u8, sizeof(NcaSectionHeaderBlock)> array{};
|
||||
if (sizeof(NcaSectionHeaderBlock) !=
|
||||
file.ReadBytes(array.data(), sizeof(NcaSectionHeaderBlock)))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
NcaSectionHeaderBlock block{};
|
||||
std::memcpy(&block, array.data(), sizeof(NcaSectionHeaderBlock));
|
||||
|
||||
if (block.filesystem_type == NcaSectionFilesystemType::ROMFS) {
|
||||
romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
|
||||
romfs_size =
|
||||
header.section_tables[i].media_end_offset * MEDIA_OFFSET_MULTIPLIER - romfs_offset;
|
||||
} else if (block.filesystem_type == NcaSectionFilesystemType::PFS0) {
|
||||
Pfs0Superblock sb{};
|
||||
// Seek back to beginning of this section.
|
||||
file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET);
|
||||
if (sizeof(Pfs0Superblock) != file.ReadBytes(&sb, sizeof(Pfs0Superblock)))
|
||||
LOG_CRITICAL(Loader, "File reader errored out during header read.");
|
||||
|
||||
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
|
||||
MEDIA_OFFSET_MULTIPLIER) +
|
||||
sb.pfs0_header_offset;
|
||||
FileSys::PartitionFilesystem npfs{};
|
||||
ResultStatus status = npfs.Load(path, offset);
|
||||
|
||||
if (status == ResultStatus::Success) {
|
||||
pfs.emplace_back(std::move(npfs));
|
||||
pfs_offset.emplace_back(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < pfs.size(); ++i) {
|
||||
if (IsPfsExeFs(pfs[i]))
|
||||
exefs_id = i;
|
||||
}
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
FileSys::PartitionFilesystem Nca::GetPfs(u8 id) const {
|
||||
return pfs[id];
|
||||
}
|
||||
|
||||
u64 Nca::GetExeFsFileOffset(const std::string& file_name) const {
|
||||
if (exefs_id == boost::none)
|
||||
return 0;
|
||||
return pfs[*exefs_id].GetFileOffset(file_name) + pfs_offset[*exefs_id];
|
||||
}
|
||||
|
||||
u64 Nca::GetExeFsFileSize(const std::string& file_name) const {
|
||||
if (exefs_id == boost::none)
|
||||
return 0;
|
||||
return pfs[*exefs_id].GetFileSize(file_name);
|
||||
}
|
||||
|
||||
u64 Nca::GetRomFsOffset() const {
|
||||
return romfs_offset;
|
||||
}
|
||||
|
||||
u64 Nca::GetRomFsSize() const {
|
||||
return romfs_size;
|
||||
}
|
||||
|
||||
std::vector<u8> Nca::GetExeFsFile(const std::string& file_name) {
|
||||
std::vector<u8> out(GetExeFsFileSize(file_name));
|
||||
file.Seek(GetExeFsFileOffset(file_name), SEEK_SET);
|
||||
file.ReadBytes(out.data(), GetExeFsFileSize(file_name));
|
||||
return out;
|
||||
}
|
||||
|
||||
AppLoader_NCA::AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath)
|
||||
: AppLoader(std::move(file)), filepath(std::move(filepath)) {}
|
||||
|
||||
FileType AppLoader_NCA::IdentifyType(FileUtil::IOFile& file, const std::string&) {
|
||||
file.Seek(0, SEEK_SET);
|
||||
std::array<u8, 0x400> header_enc_array{};
|
||||
if (0x400 != file.ReadBytes(header_enc_array.data(), 0x400))
|
||||
return FileType::Error;
|
||||
|
||||
if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program)
|
||||
// TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support.
|
||||
NcaHeader header{};
|
||||
std::memcpy(&header, header_enc_array.data(), sizeof(NcaHeader));
|
||||
|
||||
if (IsValidNca(header) && header.content_type == NcaContentType::Program)
|
||||
return FileType::NCA;
|
||||
|
||||
return FileType::Error;
|
||||
@@ -36,22 +233,17 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
if (is_loaded) {
|
||||
return ResultStatus::ErrorAlreadyLoaded;
|
||||
}
|
||||
if (!file.IsOpen()) {
|
||||
return ResultStatus::Error;
|
||||
}
|
||||
|
||||
nca = std::make_unique<FileSys::NCA>(file);
|
||||
ResultStatus result = nca->GetStatus();
|
||||
nca = std::make_unique<Nca>();
|
||||
ResultStatus result = nca->Load(std::move(file), filepath);
|
||||
if (result != ResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (nca->GetType() != FileSys::NCAContentType::Program)
|
||||
return ResultStatus::ErrorInvalidFormat;
|
||||
|
||||
auto exefs = nca->GetExeFS();
|
||||
|
||||
if (exefs == nullptr)
|
||||
return ResultStatus::ErrorInvalidFormat;
|
||||
|
||||
result = metadata.Load(exefs->GetFile("main.npdm"));
|
||||
result = metadata.Load(nca->GetExeFsFile("main.npdm"));
|
||||
if (result != ResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
@@ -66,10 +258,11 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
|
||||
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
|
||||
const VAddr load_addr = next_load_addr;
|
||||
|
||||
next_load_addr = AppLoader_NSO::LoadModule(exefs->GetFile(module), load_addr);
|
||||
next_load_addr = AppLoader_NSO::LoadModule(module, nca->GetExeFsFile(module), load_addr);
|
||||
if (next_load_addr) {
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
|
||||
} else {
|
||||
next_load_addr = load_addr;
|
||||
}
|
||||
@@ -83,11 +276,28 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(),
|
||||
metadata.GetMainThreadStackSize());
|
||||
|
||||
is_loaded = true;
|
||||
if (nca->GetRomFsSize() > 0)
|
||||
Service::FileSystem::RegisterFileSystem(std::make_unique<FileSys::RomFS_Factory>(*this),
|
||||
Service::FileSystem::Type::RomFS);
|
||||
|
||||
const auto romfs = nca->GetRomFS();
|
||||
if (romfs != nullptr)
|
||||
Service::FileSystem::RegisterRomFS(romfs);
|
||||
is_loaded = true;
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NCA::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
|
||||
u64& size) {
|
||||
if (nca->GetRomFsSize() == 0) {
|
||||
LOG_DEBUG(Loader, "No RomFS available");
|
||||
return ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb");
|
||||
|
||||
offset = nca->GetRomFsOffset();
|
||||
size = nca->GetRomFsSize();
|
||||
|
||||
LOG_DEBUG(Loader, "RomFS offset: 0x{:016X}", offset);
|
||||
LOG_DEBUG(Loader, "RomFS size: 0x{:016X}", size);
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
@@ -6,37 +6,44 @@
|
||||
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
class Nca;
|
||||
|
||||
/// Loads an NCA file
|
||||
class AppLoader_NCA final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_NCA(FileSys::VirtualFile file);
|
||||
AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath);
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
* @param file std::shared_ptr<VfsFile> open file
|
||||
* @param file FileUtil::IOFile open file
|
||||
* @param filepath Path of the file that we are opening.
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
|
||||
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
return IdentifyType(file, filepath);
|
||||
}
|
||||
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
|
||||
|
||||
ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
|
||||
u64& size) override;
|
||||
|
||||
~AppLoader_NCA();
|
||||
|
||||
private:
|
||||
std::string filepath;
|
||||
FileSys::ProgramMetadata metadata;
|
||||
|
||||
std::unique_ptr<FileSys::NCA> nca;
|
||||
std::unique_ptr<Nca> nca;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/loader/nro.h"
|
||||
@@ -47,12 +48,14 @@ struct ModHeader {
|
||||
};
|
||||
static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
|
||||
|
||||
AppLoader_NRO::AppLoader_NRO(FileSys::VirtualFile file) : AppLoader(file) {}
|
||||
AppLoader_NRO::AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath)
|
||||
: AppLoader(std::move(file)), filepath(std::move(filepath)) {}
|
||||
|
||||
FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
FileType AppLoader_NRO::IdentifyType(FileUtil::IOFile& file, const std::string&) {
|
||||
// Read NSO header
|
||||
NroHeader nro_header{};
|
||||
if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
|
||||
file.Seek(0, SEEK_SET);
|
||||
if (sizeof(NroHeader) != file.ReadBytes(&nro_header, sizeof(NroHeader))) {
|
||||
return FileType::Error;
|
||||
}
|
||||
if (nro_header.magic == Common::MakeMagic('N', 'R', 'O', '0')) {
|
||||
@@ -65,10 +68,16 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||
}
|
||||
|
||||
bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
|
||||
bool AppLoader_NRO::LoadNro(const std::string& path, VAddr load_base) {
|
||||
FileUtil::IOFile file(path, "rb");
|
||||
if (!file.IsOpen()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Read NSO header
|
||||
NroHeader nro_header{};
|
||||
if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
|
||||
file.Seek(0, SEEK_SET);
|
||||
if (sizeof(NroHeader) != file.ReadBytes(&nro_header, sizeof(NroHeader))) {
|
||||
return {};
|
||||
}
|
||||
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
|
||||
@@ -77,9 +86,10 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
|
||||
|
||||
// Build program image
|
||||
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
|
||||
std::vector<u8> program_image = file->ReadBytes(PageAlignSize(nro_header.file_size));
|
||||
if (program_image.size() != PageAlignSize(nro_header.file_size))
|
||||
return {};
|
||||
std::vector<u8> program_image;
|
||||
program_image.resize(PageAlignSize(nro_header.file_size));
|
||||
file.Seek(0, SEEK_SET);
|
||||
file.ReadBytes(program_image.data(), nro_header.file_size);
|
||||
|
||||
for (int i = 0; i < nro_header.segments.size(); ++i) {
|
||||
codeset->segments[i].addr = nro_header.segments[i].offset;
|
||||
@@ -102,10 +112,13 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
|
||||
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
|
||||
|
||||
// Load codeset for current process
|
||||
codeset->name = file->GetName();
|
||||
codeset->name = path;
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(codeset, load_base);
|
||||
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(codeset->name, load_base, load_base);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -113,11 +126,14 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
if (is_loaded) {
|
||||
return ResultStatus::ErrorAlreadyLoaded;
|
||||
}
|
||||
if (!file.IsOpen()) {
|
||||
return ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Load NRO
|
||||
static constexpr VAddr base_addr{Memory::PROCESS_IMAGE_VADDR};
|
||||
|
||||
if (!LoadNro(file, base_addr)) {
|
||||
if (!LoadNro(filepath, base_addr)) {
|
||||
return ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,23 +15,26 @@ namespace Loader {
|
||||
/// Loads an NRO file
|
||||
class AppLoader_NRO final : public AppLoader, Linker {
|
||||
public:
|
||||
AppLoader_NRO(FileSys::VirtualFile file);
|
||||
AppLoader_NRO(FileUtil::IOFile&& file, std::string filepath);
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
* @param file std::shared_ptr<VfsFile> open file
|
||||
* @param file FileUtil::IOFile open file
|
||||
* @param filepath Path of the file that we are opening.
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
|
||||
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
return IdentifyType(file, filepath);
|
||||
}
|
||||
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
|
||||
|
||||
private:
|
||||
bool LoadNro(FileSys::VirtualFile file, VAddr load_base);
|
||||
bool LoadNro(const std::string& path, VAddr load_base);
|
||||
|
||||
std::string filepath;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/loader/nso.h"
|
||||
@@ -37,7 +38,6 @@ struct NsoHeader {
|
||||
std::array<u32_le, 3> segments_compressed_size;
|
||||
};
|
||||
static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size.");
|
||||
static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable.");
|
||||
|
||||
struct ModHeader {
|
||||
u32_le magic;
|
||||
@@ -50,11 +50,15 @@ struct ModHeader {
|
||||
};
|
||||
static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
|
||||
|
||||
AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
|
||||
AppLoader_NSO::AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath)
|
||||
: AppLoader(std::move(file)), filepath(std::move(filepath)) {}
|
||||
|
||||
FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file, const std::string&) {
|
||||
u32 magic = 0;
|
||||
file->ReadObject(&magic);
|
||||
file.Seek(0, SEEK_SET);
|
||||
if (1 != file.ReadArray<u32>(&magic, 1)) {
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
if (Common::MakeMagic('N', 'S', 'O', '0') == magic) {
|
||||
return FileType::NSO;
|
||||
@@ -95,16 +99,13 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||
}
|
||||
|
||||
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
|
||||
if (file == nullptr)
|
||||
VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& file_data,
|
||||
VAddr load_base) {
|
||||
if (file_data.size() < sizeof(NsoHeader))
|
||||
return {};
|
||||
|
||||
if (file->GetSize() < sizeof(NsoHeader))
|
||||
return {};
|
||||
|
||||
NsoHeader nso_header{};
|
||||
if (sizeof(NsoHeader) != file->ReadObject(&nso_header))
|
||||
return {};
|
||||
NsoHeader nso_header;
|
||||
std::memcpy(&nso_header, file_data.data(), sizeof(NsoHeader));
|
||||
|
||||
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
return {};
|
||||
@@ -113,8 +114,9 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
|
||||
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
|
||||
std::vector<u8> program_image;
|
||||
for (int i = 0; i < nso_header.segments.size(); ++i) {
|
||||
const std::vector<u8> compressed_data =
|
||||
file->ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
||||
std::vector<u8> compressed_data(nso_header.segments_compressed_size[i]);
|
||||
for (int j = 0; j < nso_header.segments_compressed_size[i]; ++j)
|
||||
compressed_data[j] = file_data[nso_header.segments[i].offset + j];
|
||||
std::vector<u8> data = DecompressSegment(compressed_data, nso_header.segments[i]);
|
||||
program_image.resize(nso_header.segments[i].location);
|
||||
program_image.insert(program_image.end(), data.begin(), data.end());
|
||||
@@ -142,7 +144,65 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
|
||||
program_image.resize(image_size);
|
||||
|
||||
// Load codeset for current process
|
||||
codeset->name = file->GetName();
|
||||
codeset->name = name;
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(codeset, load_base);
|
||||
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(codeset->name, load_base, load_base);
|
||||
|
||||
return load_base + image_size;
|
||||
}
|
||||
|
||||
VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) {
|
||||
FileUtil::IOFile file(path, "rb");
|
||||
if (!file.IsOpen()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Read NSO header
|
||||
NsoHeader nso_header{};
|
||||
file.Seek(0, SEEK_SET);
|
||||
if (sizeof(NsoHeader) != file.ReadBytes(&nso_header, sizeof(NsoHeader))) {
|
||||
return {};
|
||||
}
|
||||
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Build program image
|
||||
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
|
||||
std::vector<u8> program_image;
|
||||
for (int i = 0; i < nso_header.segments.size(); ++i) {
|
||||
std::vector<u8> data =
|
||||
ReadSegment(file, nso_header.segments[i], nso_header.segments_compressed_size[i]);
|
||||
program_image.resize(nso_header.segments[i].location);
|
||||
program_image.insert(program_image.end(), data.begin(), data.end());
|
||||
codeset->segments[i].addr = nso_header.segments[i].location;
|
||||
codeset->segments[i].offset = nso_header.segments[i].location;
|
||||
codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
|
||||
}
|
||||
|
||||
// MOD header pointer is at .text offset + 4
|
||||
u32 module_offset;
|
||||
std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
|
||||
|
||||
// Read MOD header
|
||||
ModHeader mod_header{};
|
||||
// Default .bss to size in segment header if MOD0 section doesn't exist
|
||||
u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)};
|
||||
std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader));
|
||||
const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
|
||||
if (has_mod_header) {
|
||||
// Resize program image to include .bss section and page align each section
|
||||
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
|
||||
}
|
||||
codeset->data.size += bss_size;
|
||||
const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
|
||||
program_image.resize(image_size);
|
||||
|
||||
// Load codeset for current process
|
||||
codeset->name = path;
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(codeset, load_base);
|
||||
|
||||
@@ -153,10 +213,13 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
if (is_loaded) {
|
||||
return ResultStatus::ErrorAlreadyLoaded;
|
||||
}
|
||||
if (!file.IsOpen()) {
|
||||
return ResultStatus::Error;
|
||||
}
|
||||
|
||||
// Load module
|
||||
LoadModule(file, Memory::PROCESS_IMAGE_VADDR);
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), Memory::PROCESS_IMAGE_VADDR);
|
||||
LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR);
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", filepath, Memory::PROCESS_IMAGE_VADDR);
|
||||
|
||||
process->svc_access_mask.set();
|
||||
process->address_mappings = default_address_mappings;
|
||||
|
||||
@@ -15,22 +15,29 @@ namespace Loader {
|
||||
/// Loads an NSO file
|
||||
class AppLoader_NSO final : public AppLoader, Linker {
|
||||
public:
|
||||
explicit AppLoader_NSO(FileSys::VirtualFile file);
|
||||
AppLoader_NSO(FileUtil::IOFile&& file, std::string filepath);
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
* @param file std::shared_ptr<VfsFile> open file
|
||||
* @param file FileUtil::IOFile open file
|
||||
* @param filepath Path of the file that we are opening.
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||
static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath);
|
||||
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
return IdentifyType(file, filepath);
|
||||
}
|
||||
|
||||
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base);
|
||||
static VAddr LoadModule(const std::string& name, const std::vector<u8>& file_data,
|
||||
VAddr load_base);
|
||||
|
||||
static VAddr LoadModule(const std::string& path, VAddr load_base);
|
||||
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
|
||||
|
||||
private:
|
||||
std::string filepath;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -194,6 +194,13 @@ enum class UniformType : u64 {
|
||||
Double = 5,
|
||||
};
|
||||
|
||||
enum class IMinMaxExchange : u64 {
|
||||
None = 0,
|
||||
XLo = 1,
|
||||
XMed = 2,
|
||||
XHi = 3,
|
||||
};
|
||||
|
||||
union Instruction {
|
||||
Instruction& operator=(const Instruction& instr) {
|
||||
value = instr.value;
|
||||
@@ -278,6 +285,13 @@ union Instruction {
|
||||
BitField<49, 1, u64> negate_a;
|
||||
} alu_integer;
|
||||
|
||||
union {
|
||||
BitField<39, 3, u64> pred;
|
||||
BitField<42, 1, u64> negate_pred;
|
||||
BitField<43, 2, IMinMaxExchange> exchange;
|
||||
BitField<48, 1, u64> is_signed;
|
||||
} imnmx;
|
||||
|
||||
union {
|
||||
BitField<54, 1, u64> saturate;
|
||||
BitField<56, 1, u64> negate_a;
|
||||
@@ -700,9 +714,9 @@ private:
|
||||
INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"),
|
||||
INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"),
|
||||
INST("0011100-01100---", Id::FMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("0100110000100---", Id::IMNMX_C, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("0101110000100---", Id::IMNMX_R, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("0011100-00100---", Id::IMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("0100110000100---", Id::IMNMX_C, Type::ArithmeticInteger, "IMNMX_C"),
|
||||
INST("0101110000100---", Id::IMNMX_R, Type::ArithmeticInteger, "IMNMX_R"),
|
||||
INST("0011100-00100---", Id::IMNMX_IMM, Type::ArithmeticInteger, "IMNMX_IMM"),
|
||||
INST("0100110000000---", Id::BFE_C, Type::Bfe, "BFE_C"),
|
||||
INST("0101110000000---", Id::BFE_R, Type::Bfe, "BFE_R"),
|
||||
INST("0011100-00000---", Id::BFE_IMM, Type::Bfe, "BFE_IMM"),
|
||||
|
||||
@@ -775,10 +775,13 @@ void RasterizerOpenGL::SyncCullMode() {
|
||||
state.cull.front_face = MaxwellToGL::FrontFace(regs.cull.front_face);
|
||||
state.cull.mode = MaxwellToGL::CullFace(regs.cull.cull_face);
|
||||
|
||||
const bool flip_triangles{regs.screen_y_control.triangle_rast_flip == 0 ||
|
||||
regs.viewport_transform[0].scale_y < 0.0f};
|
||||
|
||||
// If the GPU is configured to flip the rasterized triangles, then we need to flip the
|
||||
// notion of front and back. Note: We flip the triangles when the value of the register is 0
|
||||
// because OpenGL already does it for us.
|
||||
if (regs.screen_y_control.triangle_rast_flip == 0) {
|
||||
if (flip_triangles) {
|
||||
if (state.cull.front_face == GL_CCW)
|
||||
state.cull.front_face = GL_CW;
|
||||
else if (state.cull.front_face == GL_CW)
|
||||
|
||||
@@ -102,6 +102,8 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
{GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // DXT45
|
||||
{GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN1
|
||||
{GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // BC7U
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4
|
||||
|
||||
// DepthStencil formats
|
||||
@@ -191,8 +193,9 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
|
||||
MortonCopy<true, PixelFormat::R11FG11FB10F>, MortonCopy<true, PixelFormat::RGBA32UI>,
|
||||
MortonCopy<true, PixelFormat::DXT1>, MortonCopy<true, PixelFormat::DXT23>,
|
||||
MortonCopy<true, PixelFormat::DXT45>, MortonCopy<true, PixelFormat::DXN1>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4>, MortonCopy<true, PixelFormat::Z24S8>,
|
||||
MortonCopy<true, PixelFormat::S8Z24>, MortonCopy<true, PixelFormat::Z32F>,
|
||||
MortonCopy<true, PixelFormat::BC7U>, MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
|
||||
MortonCopy<true, PixelFormat::Z24S8>, MortonCopy<true, PixelFormat::S8Z24>,
|
||||
MortonCopy<true, PixelFormat::Z32F>,
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
|
||||
@@ -206,7 +209,8 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr),
|
||||
MortonCopy<false, PixelFormat::RGBA16F>,
|
||||
MortonCopy<false, PixelFormat::R11FG11FB10F>,
|
||||
MortonCopy<false, PixelFormat::RGBA32UI>,
|
||||
// TODO(Subv): Swizzling the DXT1/DXT23/DXT45/DXN1 formats is not yet supported
|
||||
// TODO(Subv): Swizzling the DXT1/DXT23/DXT45/DXN1/BC7U formats is not yet supported
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
|
||||
@@ -35,14 +35,15 @@ struct SurfaceParams {
|
||||
DXT23 = 9,
|
||||
DXT45 = 10,
|
||||
DXN1 = 11, // This is also known as BC4
|
||||
ASTC_2D_4X4 = 12,
|
||||
BC7U = 12,
|
||||
ASTC_2D_4X4 = 13,
|
||||
|
||||
MaxColorFormat,
|
||||
|
||||
// DepthStencil formats
|
||||
Z24S8 = 13,
|
||||
S8Z24 = 14,
|
||||
Z32F = 15,
|
||||
Z24S8 = 14,
|
||||
S8Z24 = 15,
|
||||
Z32F = 16,
|
||||
|
||||
MaxDepthStencilFormat,
|
||||
|
||||
@@ -92,6 +93,7 @@ struct SurfaceParams {
|
||||
4, // DXT23
|
||||
4, // DXT45
|
||||
4, // DXN1
|
||||
4, // BC7U
|
||||
4, // ASTC_2D_4X4
|
||||
1, // Z24S8
|
||||
1, // S8Z24
|
||||
@@ -119,6 +121,7 @@ struct SurfaceParams {
|
||||
128, // DXT23
|
||||
128, // DXT45
|
||||
64, // DXN1
|
||||
128, // BC7U
|
||||
32, // ASTC_2D_4X4
|
||||
32, // Z24S8
|
||||
32, // S8Z24
|
||||
@@ -192,6 +195,8 @@ struct SurfaceParams {
|
||||
return PixelFormat::DXT45;
|
||||
case Tegra::Texture::TextureFormat::DXN1:
|
||||
return PixelFormat::DXN1;
|
||||
case Tegra::Texture::TextureFormat::BC7U:
|
||||
return PixelFormat::BC7U;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
|
||||
return PixelFormat::ASTC_2D_4X4;
|
||||
default:
|
||||
@@ -227,6 +232,8 @@ struct SurfaceParams {
|
||||
return Tegra::Texture::TextureFormat::DXT45;
|
||||
case PixelFormat::DXN1:
|
||||
return Tegra::Texture::TextureFormat::DXN1;
|
||||
case PixelFormat::BC7U:
|
||||
return Tegra::Texture::TextureFormat::BC7U;
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
return Tegra::Texture::TextureFormat::ASTC_2D_4X4;
|
||||
default:
|
||||
|
||||
@@ -1127,6 +1127,20 @@ private:
|
||||
WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::IMNMX_C:
|
||||
case OpCode::Id::IMNMX_R:
|
||||
case OpCode::Id::IMNMX_IMM: {
|
||||
ASSERT_MSG(instr.imnmx.exchange == Tegra::Shader::IMinMaxExchange::None,
|
||||
"Unimplemented");
|
||||
std::string condition =
|
||||
GetPredicateCondition(instr.imnmx.pred, instr.imnmx.negate_pred != 0);
|
||||
std::string parameters = op_a + ',' + op_b;
|
||||
regs.SetRegisterToInteger(instr.gpr0, instr.imnmx.is_signed, 0,
|
||||
'(' + condition + ") ? min(" + parameters + ") : max(" +
|
||||
parameters + ')',
|
||||
1, 1);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}",
|
||||
opcode->GetName());
|
||||
|
||||
@@ -52,6 +52,7 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||
return 8;
|
||||
case TextureFormat::DXT23:
|
||||
case TextureFormat::DXT45:
|
||||
case TextureFormat::BC7U:
|
||||
// In this case a 'pixel' actually refers to a 4x4 tile.
|
||||
return 16;
|
||||
case TextureFormat::ASTC_2D_4X4:
|
||||
@@ -98,6 +99,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width,
|
||||
case TextureFormat::DXT23:
|
||||
case TextureFormat::DXT45:
|
||||
case TextureFormat::DXN1:
|
||||
case TextureFormat::BC7U:
|
||||
// In the DXT and DXN formats, each 4x4 tile is swizzled instead of just individual pixel
|
||||
// values.
|
||||
CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data,
|
||||
@@ -155,6 +157,7 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
|
||||
case TextureFormat::DXT23:
|
||||
case TextureFormat::DXT45:
|
||||
case TextureFormat::DXN1:
|
||||
case TextureFormat::BC7U:
|
||||
case TextureFormat::ASTC_2D_4X4:
|
||||
case TextureFormat::A8R8G8B8:
|
||||
case TextureFormat::A2B10G10R10:
|
||||
|
||||
@@ -127,13 +127,14 @@ void GRenderWindow::moveContext() {
|
||||
}
|
||||
|
||||
void GRenderWindow::SwapBuffers() {
|
||||
#if !defined(QT_NO_DEBUG)
|
||||
// Qt debug runtime prints a bogus warning on the console if you haven't called makeCurrent
|
||||
// since the last time you called swapBuffers. This presumably means something if you're using
|
||||
// QGLWidget the "regular" way, but in our multi-threaded use case is harmless since we never
|
||||
// call doneCurrent in this thread.
|
||||
// In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`,
|
||||
// since we never call `doneCurrent` in this thread.
|
||||
// However:
|
||||
// - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
|
||||
// since the last time `swapBuffers` was executed;
|
||||
// - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks.
|
||||
child->makeCurrent();
|
||||
#endif
|
||||
|
||||
child->swapBuffers();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
|
||||
namespace Debugger {
|
||||
void ToggleConsole() {
|
||||
static bool console_shown = false;
|
||||
if (console_shown == UISettings::values.show_console) {
|
||||
return;
|
||||
} else {
|
||||
console_shown = UISettings::values.show_console;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && !defined(_DEBUG)
|
||||
FILE* temp;
|
||||
if (UISettings::values.show_console) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "common/common_paths.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "game_list.h"
|
||||
#include "game_list_p.h"
|
||||
@@ -405,8 +404,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
if (!is_dir &&
|
||||
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
|
||||
std::unique_ptr<Loader::AppLoader> loader =
|
||||
Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(physical_name));
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
|
||||
if (!loader)
|
||||
return true;
|
||||
|
||||
|
||||
@@ -908,8 +908,6 @@ void GMainWindow::UpdateUITheme() {
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
||||
|
||||
MicroProfileOnThreadCreate("Frontend");
|
||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -137,12 +137,12 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
gl_context = SDL_GL_CreateContext(render_window);
|
||||
|
||||
if (gl_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting...");
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
#include "yuzu_cmd/config.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <getopt.h>
|
||||
#else
|
||||
#include <getopt.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -127,6 +125,7 @@ int main(int argc, char** argv) {
|
||||
#endif
|
||||
|
||||
Log::Filter log_filter(Log::Level::Debug);
|
||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||
Log::SetGlobalFilter(log_filter);
|
||||
|
||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
||||
@@ -142,8 +141,6 @@ int main(int argc, char** argv) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||
|
||||
// Apply the command line arguments
|
||||
Settings::values.gdbstub_port = gdb_port;
|
||||
Settings::values.use_gdbstub = use_gdbstub;
|
||||
|
||||
Reference in New Issue
Block a user