fs: Add path class
parent
54372fdff5
commit
2c049ae06d
@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct ReadOption {
|
||||
u32 _value;
|
||||
|
||||
static const ReadOption None;
|
||||
};
|
||||
|
||||
enum ReadOptionFlag : u32 {
|
||||
ReadOptionFlag_None = (0 << 0),
|
||||
};
|
||||
|
||||
inline constexpr const ReadOption ReadOption::None = {ReadOptionFlag_None};
|
||||
|
||||
inline constexpr bool operator==(const ReadOption& lhs, const ReadOption& rhs) {
|
||||
return lhs._value == rhs._value;
|
||||
}
|
||||
|
||||
inline constexpr bool operator!=(const ReadOption& lhs, const ReadOption& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
static_assert(sizeof(ReadOption) == sizeof(u32));
|
||||
|
||||
enum WriteOptionFlag : u32 {
|
||||
WriteOptionFlag_None = (0 << 0),
|
||||
WriteOptionFlag_Flush = (1 << 0),
|
||||
};
|
||||
|
||||
struct WriteOption {
|
||||
u32 _value;
|
||||
|
||||
constexpr inline bool HasFlushFlag() const {
|
||||
return _value & WriteOptionFlag_Flush;
|
||||
}
|
||||
|
||||
static const WriteOption None;
|
||||
static const WriteOption Flush;
|
||||
};
|
||||
|
||||
inline constexpr const WriteOption WriteOption::None = {WriteOptionFlag_None};
|
||||
inline constexpr const WriteOption WriteOption::Flush = {WriteOptionFlag_Flush};
|
||||
|
||||
inline constexpr bool operator==(const WriteOption& lhs, const WriteOption& rhs) {
|
||||
return lhs._value == rhs._value;
|
||||
}
|
||||
|
||||
inline constexpr bool operator!=(const WriteOption& lhs, const WriteOption& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
static_assert(sizeof(WriteOption) == sizeof(u32));
|
||||
|
||||
struct FileHandle {
|
||||
void* handle;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "common/alignment.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
std::mutex g_mutex;
|
||||
|
||||
constexpr size_t RequiredAlignment = alignof(u64);
|
||||
|
||||
void* AllocateUnsafe(size_t size) {
|
||||
/* Allocate. */
|
||||
void* const ptr = ::operator new(size, std::align_val_t{RequiredAlignment});
|
||||
|
||||
/* Check alignment. */
|
||||
ASSERT(Common::IsAligned(reinterpret_cast<uintptr_t>(ptr), RequiredAlignment));
|
||||
|
||||
/* Return allocated pointer. */
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void DeallocateUnsafe(void* ptr, size_t size) {
|
||||
/* Deallocate the pointer. */
|
||||
::operator delete(ptr, std::align_val_t{RequiredAlignment});
|
||||
}
|
||||
|
||||
void* Allocate(size_t size) {
|
||||
/* Lock the allocator. */
|
||||
std::scoped_lock lk(g_mutex);
|
||||
|
||||
return AllocateUnsafe(size);
|
||||
}
|
||||
|
||||
void Deallocate(void* ptr, size_t size) {
|
||||
/* If the pointer is non-null, deallocate it. */
|
||||
if (ptr != nullptr) {
|
||||
/* Lock the allocator. */
|
||||
std::scoped_lock lk(g_mutex);
|
||||
|
||||
DeallocateUnsafe(ptr, size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class OperationId : s64 {
|
||||
FillZero = 0,
|
||||
DestroySignature = 1,
|
||||
Invalidate = 2,
|
||||
QueryRange = 3,
|
||||
QueryUnpreparedRange = 4,
|
||||
QueryLazyLoadCompletionRate = 5,
|
||||
SetLazyLoadPriority = 6,
|
||||
|
||||
ReadLazyLoadFileForciblyForDebug = 10001,
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
@ -0,0 +1,570 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fs_memory_management.h"
|
||||
#include "core/file_sys/fs_path_utility.h"
|
||||
#include "core/file_sys/fs_string_util.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
class DirectoryPathParser;
|
||||
|
||||
class Path {
|
||||
YUZU_NON_COPYABLE(Path);
|
||||
YUZU_NON_MOVEABLE(Path);
|
||||
|
||||
private:
|
||||
static constexpr const char* EmptyPath = "";
|
||||
static constexpr size_t WriteBufferAlignmentLength = 8;
|
||||
|
||||
private:
|
||||
friend class DirectoryPathParser;
|
||||
|
||||
public:
|
||||
class WriteBuffer {
|
||||
YUZU_NON_COPYABLE(WriteBuffer);
|
||||
|
||||
private:
|
||||
char* m_buffer;
|
||||
size_t m_length_and_is_normalized;
|
||||
|
||||
public:
|
||||
constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) { /* ... */
|
||||
}
|
||||
|
||||
constexpr ~WriteBuffer() {
|
||||
if (m_buffer != nullptr) {
|
||||
Deallocate(m_buffer, this->GetLength());
|
||||
this->ResetBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr WriteBuffer(WriteBuffer&& rhs)
|
||||
: m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) {
|
||||
rhs.ResetBuffer();
|
||||
}
|
||||
|
||||
constexpr WriteBuffer& operator=(WriteBuffer&& rhs) {
|
||||
if (m_buffer != nullptr) {
|
||||
Deallocate(m_buffer, this->GetLength());
|
||||
}
|
||||
|
||||
m_buffer = rhs.m_buffer;
|
||||
m_length_and_is_normalized = rhs.m_length_and_is_normalized;
|
||||
|
||||
rhs.ResetBuffer();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr void ResetBuffer() {
|
||||
m_buffer = nullptr;
|
||||
this->SetLength(0);
|
||||
}
|
||||
|
||||
constexpr char* Get() const {
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
constexpr size_t GetLength() const {
|
||||
return m_length_and_is_normalized >> 1;
|
||||
}
|
||||
|
||||
constexpr bool IsNormalized() const {
|
||||
return static_cast<bool>(m_length_and_is_normalized & 1);
|
||||
}
|
||||
|
||||
constexpr void SetNormalized() {
|
||||
m_length_and_is_normalized |= static_cast<size_t>(1);
|
||||
}
|
||||
|
||||
constexpr void SetNotNormalized() {
|
||||
m_length_and_is_normalized &= ~static_cast<size_t>(1);
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr WriteBuffer(char* buffer, size_t length)
|
||||
: m_buffer(buffer), m_length_and_is_normalized(0) {
|
||||
this->SetLength(length);
|
||||
}
|
||||
|
||||
public:
|
||||
static WriteBuffer Make(size_t length) {
|
||||
if (void* alloc = Allocate(length); alloc != nullptr) {
|
||||
return WriteBuffer(static_cast<char*>(alloc), length);
|
||||
} else {
|
||||
return WriteBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr void SetLength(size_t size) {
|
||||
m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
const char* m_str;
|
||||
WriteBuffer m_write_buffer;
|
||||
|
||||
public:
|
||||
constexpr Path() : m_str(EmptyPath), m_write_buffer() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
constexpr Path(const char* s) : m_str(s), m_write_buffer() {
|
||||
m_write_buffer.SetNormalized();
|
||||
}
|
||||
|
||||
constexpr ~Path() { /* ... */
|
||||
}
|
||||
|
||||
constexpr Result SetShallowBuffer(const char* buffer) {
|
||||
/* Check pre-conditions. */
|
||||
ASSERT(m_write_buffer.GetLength() == 0);
|
||||
|
||||
/* Check the buffer is valid. */
|
||||
R_UNLESS(buffer != nullptr, ResultNullptrArgument);
|
||||
|
||||
/* Set buffer. */
|
||||
this->SetReadOnlyBuffer(buffer);
|
||||
|
||||
/* Note that we're normalized. */
|
||||
this->SetNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
constexpr const char* GetString() const {
|
||||
/* Check pre-conditions. */
|
||||
ASSERT(this->IsNormalized());
|
||||
|
||||
return m_str;
|
||||
}
|
||||
|
||||
constexpr size_t GetLength() const {
|
||||
if (std::is_constant_evaluated()) {
|
||||
return Strlen(this->GetString());
|
||||
} else {
|
||||
return std::strlen(this->GetString());
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool IsEmpty() const {
|
||||
return *m_str == '\x00';
|
||||
}
|
||||
|
||||
constexpr bool IsMatchHead(const char* p, size_t len) const {
|
||||
return Strncmp(this->GetString(), p, len) == 0;
|
||||
}
|
||||
|
||||
Result Initialize(const Path& rhs) {
|
||||
/* Check the other path is normalized. */
|
||||
const bool normalized = rhs.IsNormalized();
|
||||
R_UNLESS(normalized, ResultNotNormalized);
|
||||
|
||||
/* Allocate buffer for our path. */
|
||||
const auto len = rhs.GetLength();
|
||||
R_TRY(this->Preallocate(len + 1));
|
||||
|
||||
/* Copy the path. */
|
||||
const size_t copied = Strlcpy<char>(m_write_buffer.Get(), rhs.GetString(), len + 1);
|
||||
R_UNLESS(copied == len, ResultUnexpectedInPathA);
|
||||
|
||||
/* Set normalized. */
|
||||
this->SetNormalized();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Initialize(const char* path, size_t len) {
|
||||
/* Check the path is valid. */
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
/* Initialize. */
|
||||
R_TRY(this->InitializeImpl(path, len));
|
||||
|
||||
/* Set not normalized. */
|
||||
this->SetNotNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Initialize(const char* path) {
|
||||
/* Check the path is valid. */
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
R_RETURN(this->Initialize(path, std::strlen(path)));
|
||||
}
|
||||
|
||||
Result InitializeWithReplaceBackslash(const char* path) {
|
||||
/* Check the path is valid. */
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
/* Initialize. */
|
||||
R_TRY(this->InitializeImpl(path, std::strlen(path)));
|
||||
|
||||
/* Replace slashes as desired. */
|
||||
if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) {
|
||||
Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/');
|
||||
}
|
||||
|
||||
/* Set not normalized. */
|
||||
this->SetNotNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InitializeWithReplaceForwardSlashes(const char* path) {
|
||||
/* Check the path is valid. */
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
/* Initialize. */
|
||||
R_TRY(this->InitializeImpl(path, std::strlen(path)));
|
||||
|
||||
/* Replace slashes as desired. */
|
||||
if (m_write_buffer.GetLength() > 1) {
|
||||
if (auto* p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') {
|
||||
p[0] = '\\';
|
||||
p[1] = '\\';
|
||||
}
|
||||
}
|
||||
|
||||
/* Set not normalized. */
|
||||
this->SetNotNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InitializeWithNormalization(const char* path, size_t size) {
|
||||
/* Check the path is valid. */
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
/* Initialize. */
|
||||
R_TRY(this->InitializeImpl(path, size));
|
||||
|
||||
/* Set not normalized. */
|
||||
this->SetNotNormalized();
|
||||
|
||||
/* Perform normalization. */
|
||||
PathFlags path_flags;
|
||||
if (IsPathRelative(m_str)) {
|
||||
path_flags.AllowRelativePath();
|
||||
} else if (IsWindowsPath(m_str, true)) {
|
||||
path_flags.AllowWindowsPath();
|
||||
} else {
|
||||
/* NOTE: In this case, Nintendo checks is normalized, then sets is normalized, then
|
||||
* returns success. */
|
||||
/* This seems like a bug. */
|
||||
size_t dummy;
|
||||
bool normalized;
|
||||
R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy),
|
||||
m_str));
|
||||
|
||||
this->SetNormalized();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Normalize. */
|
||||
R_TRY(this->Normalize(path_flags));
|
||||
|
||||
this->SetNormalized();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InitializeWithNormalization(const char* path) {
|
||||
/* Check the path is valid. */
|
||||
R_UNLESS(path != nullptr, ResultNullptrArgument);
|
||||
|
||||
R_RETURN(this->InitializeWithNormalization(path, std::strlen(path)));
|
||||
}
|
||||
|
||||
Result InitializeAsEmpty() {
|
||||
/* Clear our buffer. */
|
||||
this->ClearBuffer();
|
||||
|
||||
/* Set normalized. */
|
||||
this->SetNormalized();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AppendChild(const char* child) {
|
||||
/* Check the path is valid. */
|
||||
R_UNLESS(child != nullptr, ResultNullptrArgument);
|
||||
|
||||
/* Basic checks. If we hvea a path and the child is empty, we have nothing to do. */
|
||||
const char* c = child;
|
||||
if (m_str[0]) {
|
||||
/* Skip an early separator. */
|
||||
if (*c == '/') {
|
||||
++c;
|
||||
}
|
||||
|
||||
R_SUCCEED_IF(*c == '\x00');
|
||||
}
|
||||
|
||||
/* If we don't have a string, we can just initialize. */
|
||||
auto cur_len = std::strlen(m_str);
|
||||
if (cur_len == 0) {
|
||||
R_RETURN(this->Initialize(child));
|
||||
}
|
||||
|
||||
/* Remove a trailing separator. */
|
||||
if (m_str[cur_len - 1] == '/' || m_str[cur_len - 1] == '\\') {
|
||||
--cur_len;
|
||||
}
|
||||
|
||||
/* Get the child path's length. */
|
||||
auto child_len = std::strlen(c);
|
||||
|
||||
/* Reset our write buffer. */
|
||||
WriteBuffer old_write_buffer;
|
||||
if (m_write_buffer.Get() != nullptr) {
|
||||
old_write_buffer = std::move(m_write_buffer);
|
||||
this->ClearBuffer();
|
||||
}
|
||||
|
||||
/* Pre-allocate the new buffer. */
|
||||
R_TRY(this->Preallocate(cur_len + 1 + child_len + 1));
|
||||
|
||||
/* Get our write buffer. */
|
||||
auto* dst = m_write_buffer.Get();
|
||||
if (old_write_buffer.Get() != nullptr && cur_len > 0) {
|
||||
Strlcpy<char>(dst, old_write_buffer.Get(), cur_len + 1);
|
||||
}
|
||||
|
||||
/* Add separator. */
|
||||
dst[cur_len] = '/';
|
||||
|
||||
/* Copy the child path. */
|
||||
const size_t copied = Strlcpy<char>(dst + cur_len + 1, c, child_len + 1);
|
||||
R_UNLESS(copied == child_len, ResultUnexpectedInPathA);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AppendChild(const Path& rhs) {
|
||||
R_RETURN(this->AppendChild(rhs.GetString()));
|
||||
}
|
||||
|
||||
Result Combine(const Path& parent, const Path& child) {
|
||||
/* Get the lengths. */
|
||||
const auto p_len = parent.GetLength();
|
||||
const auto c_len = child.GetLength();
|
||||
|
||||
/* Allocate our buffer. */
|
||||
R_TRY(this->Preallocate(p_len + c_len + 1));
|
||||
|
||||
/* Initialize as parent. */
|
||||
R_TRY(this->Initialize(parent));
|
||||
|
||||
/* If we're empty, we can just initialize as child. */
|
||||
if (this->IsEmpty()) {
|
||||
R_TRY(this->Initialize(child));
|
||||
} else {
|
||||
/* Otherwise, we should append the child. */
|
||||
R_TRY(this->AppendChild(child));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result RemoveChild() {
|
||||
/* If we don't have a write-buffer, ensure that we have one. */
|
||||
if (m_write_buffer.Get() == nullptr) {
|
||||
if (const auto len = std::strlen(m_str); len > 0) {
|
||||
R_TRY(this->Preallocate(len));
|
||||
Strlcpy<char>(m_write_buffer.Get(), m_str, len + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that it's possible for us to remove a child. */
|
||||
auto* p = m_write_buffer.Get();
|
||||
s32 len = std::strlen(p);
|
||||
R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), ResultNotImplemented);
|
||||
|
||||
/* Handle a trailing separator. */
|
||||
if (len > 0 && (p[len - 1] == '\\' || p[len - 1] == '/')) {
|
||||
--len;
|
||||
}
|
||||
|
||||
/* Remove the child path segment. */
|
||||
while ((--len) >= 0 && p[len]) {
|
||||
if (p[len] == '/' || p[len] == '\\') {
|
||||
if (len > 0) {
|
||||
p[len] = 0;
|
||||
} else {
|
||||
p[1] = 0;
|
||||
len = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that length remains > 0. */
|
||||
R_UNLESS(len > 0, ResultNotImplemented);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Normalize(const PathFlags& flags) {
|
||||
/* If we're already normalized, nothing to do. */
|
||||
R_SUCCEED_IF(this->IsNormalized());
|
||||
|
||||
/* Check if we're normalized. */
|
||||
bool normalized;
|
||||
size_t dummy;
|
||||
R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str,
|
||||
flags));
|
||||
|
||||
/* If we're not normalized, normalize. */
|
||||
if (!normalized) {
|
||||
/* Determine necessary buffer length. */
|
||||
auto len = m_write_buffer.GetLength();
|
||||
if (flags.IsRelativePathAllowed() && IsPathRelative(m_str)) {
|
||||
len += 2;
|
||||
}
|
||||
if (flags.IsWindowsPathAllowed() && IsWindowsPath(m_str, true)) {
|
||||
len += 1;
|
||||
}
|
||||
|
||||
/* Allocate a new buffer. */
|
||||
const size_t size = Common::AlignUp(len, WriteBufferAlignmentLength);
|
||||
auto buf = WriteBuffer::Make(size);
|
||||
R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
|
||||
|
||||
/* Normalize into it. */
|
||||
R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(),
|
||||
m_write_buffer.GetLength(), flags));
|
||||
|
||||
/* Set the normalized buffer as our buffer. */
|
||||
this->SetModifiableBuffer(std::move(buf));
|
||||
}
|
||||
|
||||
/* Set normalized. */
|
||||
this->SetNormalized();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
private:
|
||||
void ClearBuffer() {
|
||||
m_write_buffer.ResetBuffer();
|
||||
m_str = EmptyPath;
|
||||
}
|
||||
|
||||
void SetModifiableBuffer(WriteBuffer&& buffer) {
|
||||
/* Check pre-conditions. */
|
||||
ASSERT(buffer.Get() != nullptr);
|
||||
ASSERT(buffer.GetLength() > 0);
|
||||
ASSERT(Common::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength));
|
||||
|
||||
/* Get whether we're normalized. */
|
||||
if (m_write_buffer.IsNormalized()) {
|
||||
buffer.SetNormalized();
|
||||
} else {
|
||||
buffer.SetNotNormalized();
|
||||
}
|
||||
|
||||
/* Set write buffer. */
|
||||
m_write_buffer = std::move(buffer);
|
||||
m_str = m_write_buffer.Get();
|
||||
}
|
||||
|
||||
constexpr void SetReadOnlyBuffer(const char* buffer) {
|
||||
m_str = buffer;
|
||||
m_write_buffer.ResetBuffer();
|
||||
}
|
||||
|
||||
Result Preallocate(size_t length) {
|
||||
/* Allocate additional space, if needed. */
|
||||
if (length > m_write_buffer.GetLength()) {
|
||||
/* Allocate buffer. */
|
||||
const size_t size = Common::AlignUp(length, WriteBufferAlignmentLength);
|
||||
auto buf = WriteBuffer::Make(size);
|
||||
R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
|
||||
|
||||
/* Set write buffer. */
|
||||
this->SetModifiableBuffer(std::move(buf));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InitializeImpl(const char* path, size_t size) {
|
||||
if (size > 0 && path[0]) {
|
||||
/* Pre allocate a buffer for the path. */
|
||||
R_TRY(this->Preallocate(size + 1));
|
||||
|
||||
/* Copy the path. */
|
||||
const size_t copied = Strlcpy<char>(m_write_buffer.Get(), path, size + 1);
|
||||
R_UNLESS(copied >= size, ResultUnexpectedInPathA);
|
||||
} else {
|
||||
/* We can just clear the buffer. */
|
||||
this->ClearBuffer();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
constexpr char* GetWriteBuffer() {
|
||||
ASSERT(m_write_buffer.Get() != nullptr);
|
||||
return m_write_buffer.Get();
|
||||
}
|
||||
|
||||
constexpr size_t GetWriteBufferLength() const {
|
||||
return m_write_buffer.GetLength();
|
||||
}
|
||||
|
||||
constexpr bool IsNormalized() const {
|
||||
return m_write_buffer.IsNormalized();
|
||||
}
|
||||
|
||||
constexpr void SetNormalized() {
|
||||
m_write_buffer.SetNormalized();
|
||||
}
|
||||
|
||||
constexpr void SetNotNormalized() {
|
||||
m_write_buffer.SetNotNormalized();
|
||||
}
|
||||
|
||||
public:
|
||||
bool operator==(const FileSys::Path& rhs) const {
|
||||
return std::strcmp(this->GetString(), rhs.GetString()) == 0;
|
||||
}
|
||||
bool operator!=(const FileSys::Path& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
bool operator==(const char* p) const {
|
||||
return std::strcmp(this->GetString(), p) == 0;
|
||||
}
|
||||
bool operator!=(const char* p) const {
|
||||
return !(*this == p);
|
||||
}
|
||||
};
|
||||
|
||||
inline Result SetUpFixedPath(FileSys::Path* out, const char* s) {
|
||||
/* Verify the path is normalized. */
|
||||
bool normalized;
|
||||
size_t dummy;
|
||||
R_TRY(PathNormalizer::IsNormalized(std::addressof(normalized), std::addressof(dummy), s));
|
||||
|
||||
R_UNLESS(normalized, ResultInvalidPathFormat);
|
||||
|
||||
/* Set the fixed path. */
|
||||
R_RETURN(out->SetShallowBuffer(s));
|
||||
}
|
||||
|
||||
constexpr inline bool IsWindowsDriveRootPath(const FileSys::Path& path) {
|
||||
const char* const str = path.GetString();
|
||||
return IsWindowsDrive(str) &&
|
||||
(str[2] == StringTraits::DirectorySeparator ||
|
||||
str[2] == StringTraits::AlternateDirectorySeparator) &&
|
||||
str[3] == StringTraits::NullTerminator;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,241 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
template <typename T>
|
||||
constexpr int Strlen(const T* str) {
|
||||
ASSERT(str != nullptr);
|
||||
|
||||
int length = 0;
|
||||
while (*str++) {
|
||||
++length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr int Strnlen(const T* str, int count) {
|
||||
ASSERT(str != nullptr);
|
||||
ASSERT(count >= 0);
|
||||
|
||||
int length = 0;
|
||||
while (count-- && *str++) {
|
||||
++length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr int Strncmp(const T* lhs, const T* rhs, int count) {
|
||||
ASSERT(lhs != nullptr);
|
||||
ASSERT(rhs != nullptr);
|
||||
ASSERT(count >= 0);
|
||||
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
T l, r;
|
||||
do {
|
||||
l = *(lhs++);
|
||||
r = *(rhs++);
|
||||
} while (l && (l == r) && (--count));
|
||||
|
||||
return l - r;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr int Strlcpy(T* dst, const T* src, int count) {
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(src != nullptr);
|
||||
|
||||
const T* cur = src;
|
||||
if (count > 0) {
|
||||
while ((--count) && *cur) {
|
||||
*(dst++) = *(cur++);
|
||||
}
|
||||
*dst = 0;
|
||||
}
|
||||
|
||||
while (*cur) {
|
||||
cur++;
|
||||
}
|
||||
|
||||
return static_cast<int>(cur - src);
|
||||
}
|
||||
|
||||
/* std::size() does not support zero-size C arrays. We're fixing that. */
|
||||
template <class C>
|
||||
constexpr auto size(const C& c) -> decltype(c.size()) {
|
||||
return std::size(c);
|
||||
}
|
||||
|
||||
template <class C>
|
||||
constexpr std::size_t size(const C& c) {
|
||||
if constexpr (sizeof(C) == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return std::size(c);
|
||||
}
|
||||
}
|
||||
|
||||
enum CharacterEncodingResult {
|
||||
CharacterEncodingResult_Success = 0,
|
||||
CharacterEncodingResult_InsufficientLength = 1,
|
||||
CharacterEncodingResult_InvalidFormat = 2,
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
class CharacterEncodingHelper {
|
||||
public:
|
||||
static constexpr int8_t Utf8NBytesInnerTable[0x100 + 1] = {
|
||||
-1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8,
|
||||
};
|
||||
|
||||
static constexpr char GetUtf8NBytes(size_t i) {
|
||||
return static_cast<char>(Utf8NBytesInnerTable[1 + i]);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
constexpr inline CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32* dst, const char* src) {
|
||||
/* Check pre-conditions. */
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(src != nullptr);
|
||||
|
||||
/* Perform the conversion. */
|
||||
const auto* p = src;
|
||||
switch (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[0]))) {
|
||||
case 1:
|
||||
*dst = static_cast<u32>(p[0]);
|
||||
return CharacterEncodingResult_Success;
|
||||
case 2:
|
||||
if ((static_cast<u32>(p[0]) & 0x1E) != 0) {
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) ==
|
||||
0) {
|
||||
*dst = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0);
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) {
|
||||
const u32 c = (static_cast<u32>(p[0] & 0xF) << 12) |
|
||||
(static_cast<u32>(p[1] & 0x3F) << 6) |
|
||||
(static_cast<u32>(p[2] & 0x3F) << 0);
|
||||
if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
|
||||
*dst = c;
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 4:
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) {
|
||||
const u32 c =
|
||||
(static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) |
|
||||
(static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0);
|
||||
if (c >= 0x10000 && c < 0x110000) {
|
||||
*dst = c;
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* We failed to convert. */
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
}
|
||||
|
||||
constexpr inline CharacterEncodingResult PickOutCharacterFromUtf8String(char* dst,
|
||||
const char** str) {
|
||||
/* Check pre-conditions. */
|
||||
ASSERT(dst != nullptr);
|
||||
ASSERT(str != nullptr);
|
||||
ASSERT(*str != nullptr);
|
||||
|
||||
/* Clear the output. */
|
||||
dst[0] = 0;
|
||||
dst[1] = 0;
|
||||
dst[2] = 0;
|
||||
dst[3] = 0;
|
||||
|
||||
/* Perform the conversion. */
|
||||
const auto* p = *str;
|
||||
u32 c = static_cast<u32>(*p);
|
||||
switch (impl::CharacterEncodingHelper::GetUtf8NBytes(c)) {
|
||||
case 1:
|
||||
dst[0] = (*str)[0];
|
||||
++(*str);
|
||||
break;
|
||||
case 2:
|
||||
if ((p[0] & 0x1E) != 0) {
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) ==
|
||||
0) {
|
||||
c = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0);
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
(*str) += 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 3:
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) {
|
||||
c = (static_cast<u32>(p[0] & 0xF) << 12) | (static_cast<u32>(p[1] & 0x3F) << 6) |
|
||||
(static_cast<u32>(p[2] & 0x3F) << 0);
|
||||
if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
dst[2] = (*str)[2];
|
||||
(*str) += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
case 4:
|
||||
if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 &&
|
||||
impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) {
|
||||
c = (static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) |
|
||||
(static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0);
|
||||
if (c >= 0x10000 && c < 0x110000) {
|
||||
dst[0] = (*str)[0];
|
||||
dst[1] = (*str)[1];
|
||||
dst[2] = (*str)[2];
|
||||
dst[3] = (*str)[3];
|
||||
(*str) += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
default:
|
||||
return CharacterEncodingResult_InvalidFormat;
|
||||
}
|
||||
|
||||
return CharacterEncodingResult_Success;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
Loading…
Reference in New Issue