fs: Add path class
This commit is contained in:
		@@ -19,4 +19,21 @@ inline T WrappingAdd(T lhs, T rhs) {
 | 
			
		||||
    return BitCast<T>(lhs_u + rhs_u);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
    requires(std::is_integral_v<T> && std::is_signed_v<T>)
 | 
			
		||||
inline bool CanAddWithoutOverflow(T lhs, T rhs) {
 | 
			
		||||
#ifdef _MSC_VER
 | 
			
		||||
    if (lhs >= 0 && rhs >= 0) {
 | 
			
		||||
        return WrappingAdd(lhs, rhs) >= std::max(lhs, rhs);
 | 
			
		||||
    } else if (lhs < 0 && rhs < 0) {
 | 
			
		||||
        return WrappingAdd(lhs, rhs) <= std::min(lhs, rhs);
 | 
			
		||||
    } else {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
#else
 | 
			
		||||
    T res;
 | 
			
		||||
    return !__builtin_add_overflow(lhs, rhs, &res);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Common
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ add_library(core STATIC
 | 
			
		||||
    file_sys/fs_operate_range.h
 | 
			
		||||
    file_sys/fs_path.h
 | 
			
		||||
    file_sys/fs_path_utility.h
 | 
			
		||||
    file_sys/fs_util_character_encoding.h
 | 
			
		||||
    file_sys/fs_string_util.h
 | 
			
		||||
    file_sys/fsmitm_romfsbuild.cpp
 | 
			
		||||
    file_sys/fsmitm_romfsbuild.h
 | 
			
		||||
    file_sys/fssystem/fs_i_storage.h
 | 
			
		||||
@@ -104,25 +104,10 @@ add_library(core STATIC
 | 
			
		||||
    file_sys/fssystem/fssystem_switch_storage.h
 | 
			
		||||
    file_sys/fssystem/fssystem_utility.cpp
 | 
			
		||||
    file_sys/fssystem/fssystem_utility.h
 | 
			
		||||
    file_sys/fssystem/fs_types.h
 | 
			
		||||
    file_sys/bis_factory.cpp
 | 
			
		||||
    file_sys/bis_factory.h
 | 
			
		||||
    file_sys/card_image.cpp
 | 
			
		||||
    file_sys/card_image.h
 | 
			
		||||
    file_sys/common_funcs.h
 | 
			
		||||
    file_sys/content_archive.cpp
 | 
			
		||||
    file_sys/content_archive.h
 | 
			
		||||
    file_sys/control_metadata.cpp
 | 
			
		||||
    file_sys/control_metadata.h
 | 
			
		||||
    file_sys/directory.h
 | 
			
		||||
    file_sys/errors.h
 | 
			
		||||
    file_sys/fsmitm_romfsbuild.cpp
 | 
			
		||||
    file_sys/fsmitm_romfsbuild.h
 | 
			
		||||
    file_sys/ips_layer.cpp
 | 
			
		||||
    file_sys/ips_layer.h
 | 
			
		||||
    file_sys/kernel_executable.cpp
 | 
			
		||||
    file_sys/kernel_executable.h
 | 
			
		||||
    file_sys/mode.h
 | 
			
		||||
    file_sys/nca_metadata.cpp
 | 
			
		||||
    file_sys/nca_metadata.h
 | 
			
		||||
    file_sys/partition_filesystem.cpp
 | 
			
		||||
@@ -215,7 +200,6 @@ add_library(core STATIC
 | 
			
		||||
    hle/kernel/board/nintendo/nx/secure_monitor.h
 | 
			
		||||
    hle/kernel/code_set.cpp
 | 
			
		||||
    hle/kernel/code_set.h
 | 
			
		||||
    hle/kernel/svc_results.h
 | 
			
		||||
    hle/kernel/global_scheduler_context.cpp
 | 
			
		||||
    hle/kernel/global_scheduler_context.h
 | 
			
		||||
    hle/kernel/init/init_slab_setup.cpp
 | 
			
		||||
@@ -225,11 +209,11 @@ add_library(core STATIC
 | 
			
		||||
    hle/kernel/k_address_arbiter.h
 | 
			
		||||
    hle/kernel/k_address_space_info.cpp
 | 
			
		||||
    hle/kernel/k_address_space_info.h
 | 
			
		||||
    hle/kernel/k_affinity_mask.h
 | 
			
		||||
    hle/kernel/k_auto_object.cpp
 | 
			
		||||
    hle/kernel/k_auto_object.h
 | 
			
		||||
    hle/kernel/k_auto_object_container.cpp
 | 
			
		||||
    hle/kernel/k_auto_object_container.h
 | 
			
		||||
    hle/kernel/k_affinity_mask.h
 | 
			
		||||
    hle/kernel/k_capabilities.cpp
 | 
			
		||||
    hle/kernel/k_capabilities.h
 | 
			
		||||
    hle/kernel/k_class_token.cpp
 | 
			
		||||
@@ -253,9 +237,9 @@ add_library(core STATIC
 | 
			
		||||
    hle/kernel/k_event_info.h
 | 
			
		||||
    hle/kernel/k_handle_table.cpp
 | 
			
		||||
    hle/kernel/k_handle_table.h
 | 
			
		||||
    hle/kernel/k_hardware_timer_base.h
 | 
			
		||||
    hle/kernel/k_hardware_timer.cpp
 | 
			
		||||
    hle/kernel/k_hardware_timer.h
 | 
			
		||||
    hle/kernel/k_hardware_timer_base.h
 | 
			
		||||
    hle/kernel/k_interrupt_manager.cpp
 | 
			
		||||
    hle/kernel/k_interrupt_manager.h
 | 
			
		||||
    hle/kernel/k_light_client_session.cpp
 | 
			
		||||
@@ -282,10 +266,10 @@ add_library(core STATIC
 | 
			
		||||
    hle/kernel/k_page_bitmap.h
 | 
			
		||||
    hle/kernel/k_page_buffer.cpp
 | 
			
		||||
    hle/kernel/k_page_buffer.h
 | 
			
		||||
    hle/kernel/k_page_heap.cpp
 | 
			
		||||
    hle/kernel/k_page_heap.h
 | 
			
		||||
    hle/kernel/k_page_group.cpp
 | 
			
		||||
    hle/kernel/k_page_group.h
 | 
			
		||||
    hle/kernel/k_page_heap.cpp
 | 
			
		||||
    hle/kernel/k_page_heap.h
 | 
			
		||||
    hle/kernel/k_page_table.h
 | 
			
		||||
    hle/kernel/k_page_table_base.cpp
 | 
			
		||||
    hle/kernel/k_page_table_base.h
 | 
			
		||||
@@ -350,8 +334,6 @@ add_library(core STATIC
 | 
			
		||||
    hle/kernel/slab_helpers.h
 | 
			
		||||
    hle/kernel/svc.cpp
 | 
			
		||||
    hle/kernel/svc.h
 | 
			
		||||
    hle/kernel/svc_common.h
 | 
			
		||||
    hle/kernel/svc_types.h
 | 
			
		||||
    hle/kernel/svc/svc_activity.cpp
 | 
			
		||||
    hle/kernel/svc/svc_address_arbiter.cpp
 | 
			
		||||
    hle/kernel/svc/svc_address_translation.cpp
 | 
			
		||||
@@ -389,6 +371,9 @@ add_library(core STATIC
 | 
			
		||||
    hle/kernel/svc/svc_thread_profiler.cpp
 | 
			
		||||
    hle/kernel/svc/svc_tick.cpp
 | 
			
		||||
    hle/kernel/svc/svc_transfer_memory.cpp
 | 
			
		||||
    hle/kernel/svc_common.h
 | 
			
		||||
    hle/kernel/svc_results.h
 | 
			
		||||
    hle/kernel/svc_types.h
 | 
			
		||||
    hle/result.h
 | 
			
		||||
    hle/service/acc/acc.cpp
 | 
			
		||||
    hle/service/acc/acc.h
 | 
			
		||||
@@ -519,17 +504,17 @@ add_library(core STATIC
 | 
			
		||||
    hle/service/filesystem/fsp/fs_i_filesystem.h
 | 
			
		||||
    hle/service/filesystem/fsp/fs_i_storage.cpp
 | 
			
		||||
    hle/service/filesystem/fsp/fs_i_storage.h
 | 
			
		||||
    hle/service/filesystem/fsp/fsp_ldr.cpp
 | 
			
		||||
    hle/service/filesystem/fsp/fsp_ldr.h
 | 
			
		||||
    hle/service/filesystem/fsp/fsp_pr.cpp
 | 
			
		||||
    hle/service/filesystem/fsp/fsp_pr.h
 | 
			
		||||
    hle/service/filesystem/fsp/fsp_srv.cpp
 | 
			
		||||
    hle/service/filesystem/fsp/fsp_srv.h
 | 
			
		||||
    hle/service/filesystem/fsp_ldr.cpp
 | 
			
		||||
    hle/service/filesystem/fsp_ldr.h
 | 
			
		||||
    hle/service/filesystem/fsp_pr.cpp
 | 
			
		||||
    hle/service/filesystem/fsp_pr.h
 | 
			
		||||
    hle/service/filesystem/fsp/fsp_util.h
 | 
			
		||||
    hle/service/filesystem/romfs_controller.cpp
 | 
			
		||||
    hle/service/filesystem/romfs_controller.h
 | 
			
		||||
    hle/service/filesystem/save_data_controller.cpp
 | 
			
		||||
    hle/service/filesystem/save_data_controller.h
 | 
			
		||||
    hle/service/filesystem/fsp_util.h
 | 
			
		||||
    hle/service/fgm/fgm.cpp
 | 
			
		||||
    hle/service/fgm/fgm.h
 | 
			
		||||
    hle/service/friend/friend.cpp
 | 
			
		||||
 
 | 
			
		||||
@@ -73,10 +73,21 @@ constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
 | 
			
		||||
constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
 | 
			
		||||
constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
 | 
			
		||||
constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
 | 
			
		||||
constexpr Result ResultUnexpectedInPathA{ErrorModule::FS, 5328};
 | 
			
		||||
constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
 | 
			
		||||
constexpr Result ResultInvalidPath{ErrorModule::FS, 6002};
 | 
			
		||||
constexpr Result ResultTooLongPath{ErrorModule::FS, 6003};
 | 
			
		||||
constexpr Result ResultInvalidCharacter{ErrorModule::FS, 6004};
 | 
			
		||||
constexpr Result ResultInvalidPathFormat{ErrorModule::FS, 6005};
 | 
			
		||||
constexpr Result ResultDirectoryUnobtainable{ErrorModule::FS, 6006};
 | 
			
		||||
constexpr Result ResultNotNormalized{ErrorModule::FS, 6007};
 | 
			
		||||
constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
 | 
			
		||||
constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
 | 
			
		||||
constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
 | 
			
		||||
constexpr Result ResultInvalidOpenMode{ErrorModule::FS, 6072};
 | 
			
		||||
constexpr Result ResultFileExtensionWithoutOpenModeAllowAppend{ErrorModule::FS, 6201};
 | 
			
		||||
constexpr Result ResultReadNotPermitted{ErrorModule::FS, 6202};
 | 
			
		||||
constexpr Result ResultWriteNotPermitted{ErrorModule::FS, 6203};
 | 
			
		||||
constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
 | 
			
		||||
constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
 | 
			
		||||
constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										63
									
								
								src/core/file_sys/fs_file.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/core/file_sys/fs_file.h
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										48
									
								
								src/core/file_sys/fs_memory_management.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/core/file_sys/fs_memory_management.h
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										20
									
								
								src/core/file_sys/fs_operate_range.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/core/file_sys/fs_operate_range.h
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										570
									
								
								src/core/file_sys/fs_path.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										570
									
								
								src/core/file_sys/fs_path.h
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										1240
									
								
								src/core/file_sys/fs_path_utility.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1240
									
								
								src/core/file_sys/fs_path_utility.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										241
									
								
								src/core/file_sys/fs_string_util.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								src/core/file_sys/fs_string_util.h
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#include "core/file_sys/fs_filesystem.h"
 | 
			
		||||
#include "core/file_sys/savedata_factory.h"
 | 
			
		||||
#include "core/hle/service/filesystem/fsp/fs_i_directory.h"
 | 
			
		||||
#include "core/hle/service/ipc_helpers.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,14 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "core/file_sys/fs_filesystem.h"
 | 
			
		||||
#include "core/file_sys/vfs/vfs.h"
 | 
			
		||||
#include "core/hle/service/filesystem/filesystem.h"
 | 
			
		||||
#include "core/hle/service/filesystem/fsp/fsp_util.h"
 | 
			
		||||
#include "core/hle/service/service.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
struct DirectoryEntry;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Service::FileSystem {
 | 
			
		||||
 | 
			
		||||
class IDirectory final : public ServiceFramework<IDirectory> {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user