mirror of
https://git.suyu.dev/suyu/suyu
synced 2024-12-27 11:52:31 -06:00
Merge pull request #9825 from liamwhite/object-name
kernel: add KObjectName
This commit is contained in:
commit
41183b622f
7 changed files with 265 additions and 3 deletions
|
@ -225,6 +225,8 @@ add_library(core STATIC
|
||||||
hle/kernel/k_memory_manager.h
|
hle/kernel/k_memory_manager.h
|
||||||
hle/kernel/k_memory_region.h
|
hle/kernel/k_memory_region.h
|
||||||
hle/kernel/k_memory_region_type.h
|
hle/kernel/k_memory_region_type.h
|
||||||
|
hle/kernel/k_object_name.cpp
|
||||||
|
hle/kernel/k_object_name.h
|
||||||
hle/kernel/k_page_bitmap.h
|
hle/kernel/k_page_bitmap.h
|
||||||
hle/kernel/k_page_buffer.cpp
|
hle/kernel/k_page_buffer.cpp
|
||||||
hle/kernel/k_page_buffer.h
|
hle/kernel/k_page_buffer.h
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "core/hle/kernel/k_event_info.h"
|
#include "core/hle/kernel/k_event_info.h"
|
||||||
#include "core/hle/kernel/k_memory_layout.h"
|
#include "core/hle/kernel/k_memory_layout.h"
|
||||||
#include "core/hle/kernel/k_memory_manager.h"
|
#include "core/hle/kernel/k_memory_manager.h"
|
||||||
|
#include "core/hle/kernel/k_object_name.h"
|
||||||
#include "core/hle/kernel/k_page_buffer.h"
|
#include "core/hle/kernel/k_page_buffer.h"
|
||||||
#include "core/hle/kernel/k_port.h"
|
#include "core/hle/kernel/k_port.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
@ -49,6 +50,7 @@ namespace Kernel::Init {
|
||||||
HANDLER(KThreadLocalPage, \
|
HANDLER(KThreadLocalPage, \
|
||||||
(SLAB_COUNT(KProcess) + (SLAB_COUNT(KProcess) + SLAB_COUNT(KThread)) / 8), \
|
(SLAB_COUNT(KProcess) + (SLAB_COUNT(KProcess) + SLAB_COUNT(KThread)) / 8), \
|
||||||
##__VA_ARGS__) \
|
##__VA_ARGS__) \
|
||||||
|
HANDLER(KObjectName, (SLAB_COUNT(KObjectName)), ##__VA_ARGS__) \
|
||||||
HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__) \
|
HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__) \
|
||||||
HANDLER(KEventInfo, (SLAB_COUNT(KThread) + SLAB_COUNT(KDebug)), ##__VA_ARGS__) \
|
HANDLER(KEventInfo, (SLAB_COUNT(KThread) + SLAB_COUNT(KDebug)), ##__VA_ARGS__) \
|
||||||
HANDLER(KDebug, (SLAB_COUNT(KDebug)), ##__VA_ARGS__) \
|
HANDLER(KDebug, (SLAB_COUNT(KDebug)), ##__VA_ARGS__) \
|
||||||
|
|
102
src/core/hle/kernel/k_object_name.cpp
Normal file
102
src/core/hle/kernel/k_object_name.cpp
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_object_name.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
KObjectNameGlobalData::KObjectNameGlobalData(KernelCore& kernel) : m_object_list_lock{kernel} {}
|
||||||
|
KObjectNameGlobalData::~KObjectNameGlobalData() = default;
|
||||||
|
|
||||||
|
void KObjectName::Initialize(KAutoObject* obj, const char* name) {
|
||||||
|
// Set member variables.
|
||||||
|
m_object = obj;
|
||||||
|
std::strncpy(m_name.data(), name, sizeof(m_name) - 1);
|
||||||
|
m_name[sizeof(m_name) - 1] = '\x00';
|
||||||
|
|
||||||
|
// Open a reference to the object we hold.
|
||||||
|
m_object->Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KObjectName::MatchesName(const char* name) const {
|
||||||
|
return std::strncmp(m_name.data(), name, sizeof(m_name)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result KObjectName::NewFromName(KernelCore& kernel, KAutoObject* obj, const char* name) {
|
||||||
|
// Create a new object name.
|
||||||
|
KObjectName* new_name = KObjectName::Allocate(kernel);
|
||||||
|
R_UNLESS(new_name != nullptr, ResultOutOfResource);
|
||||||
|
|
||||||
|
// Initialize the new name.
|
||||||
|
new_name->Initialize(obj, name);
|
||||||
|
|
||||||
|
// Check if there's an existing name.
|
||||||
|
{
|
||||||
|
// Get the global data.
|
||||||
|
KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()};
|
||||||
|
|
||||||
|
// Ensure we have exclusive access to the global list.
|
||||||
|
KScopedLightLock lk{gd.GetObjectListLock()};
|
||||||
|
|
||||||
|
// If the object doesn't exist, put it into the list.
|
||||||
|
KScopedAutoObject existing_object = FindImpl(kernel, name);
|
||||||
|
if (existing_object.IsNull()) {
|
||||||
|
gd.GetObjectList().push_back(*new_name);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The object already exists, which is an error condition. Perform cleanup.
|
||||||
|
obj->Close();
|
||||||
|
KObjectName::Free(kernel, new_name);
|
||||||
|
R_THROW(ResultInvalidState);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result KObjectName::Delete(KernelCore& kernel, KAutoObject* obj, const char* compare_name) {
|
||||||
|
// Get the global data.
|
||||||
|
KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()};
|
||||||
|
|
||||||
|
// Ensure we have exclusive access to the global list.
|
||||||
|
KScopedLightLock lk{gd.GetObjectListLock()};
|
||||||
|
|
||||||
|
// Find a matching entry in the list, and delete it.
|
||||||
|
for (auto& name : gd.GetObjectList()) {
|
||||||
|
if (name.MatchesName(compare_name) && obj == name.GetObject()) {
|
||||||
|
// We found a match, clean up its resources.
|
||||||
|
obj->Close();
|
||||||
|
gd.GetObjectList().erase(gd.GetObjectList().iterator_to(name));
|
||||||
|
KObjectName::Free(kernel, std::addressof(name));
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't find the object in the list.
|
||||||
|
R_THROW(ResultNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
KScopedAutoObject<KAutoObject> KObjectName::Find(KernelCore& kernel, const char* name) {
|
||||||
|
// Get the global data.
|
||||||
|
KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()};
|
||||||
|
|
||||||
|
// Ensure we have exclusive access to the global list.
|
||||||
|
KScopedLightLock lk{gd.GetObjectListLock()};
|
||||||
|
|
||||||
|
return FindImpl(kernel, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
KScopedAutoObject<KAutoObject> KObjectName::FindImpl(KernelCore& kernel, const char* compare_name) {
|
||||||
|
// Get the global data.
|
||||||
|
KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()};
|
||||||
|
|
||||||
|
// Try to find a matching object in the global list.
|
||||||
|
for (const auto& name : gd.GetObjectList()) {
|
||||||
|
if (name.MatchesName(compare_name)) {
|
||||||
|
return name.GetObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's no matching entry in the list.
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
86
src/core/hle/kernel/k_object_name.h
Normal file
86
src/core/hle/kernel/k_object_name.h
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/intrusive/list.hpp>
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_light_lock.h"
|
||||||
|
#include "core/hle/kernel/slab_helpers.h"
|
||||||
|
#include "core/hle/kernel/svc_results.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
class KObjectNameGlobalData;
|
||||||
|
|
||||||
|
class KObjectName : public KSlabAllocated<KObjectName>, public boost::intrusive::list_base_hook<> {
|
||||||
|
public:
|
||||||
|
explicit KObjectName(KernelCore&) {}
|
||||||
|
virtual ~KObjectName() = default;
|
||||||
|
|
||||||
|
static constexpr size_t NameLengthMax = 12;
|
||||||
|
using List = boost::intrusive::list<KObjectName>;
|
||||||
|
|
||||||
|
static Result NewFromName(KernelCore& kernel, KAutoObject* obj, const char* name);
|
||||||
|
static Result Delete(KernelCore& kernel, KAutoObject* obj, const char* name);
|
||||||
|
|
||||||
|
static KScopedAutoObject<KAutoObject> Find(KernelCore& kernel, const char* name);
|
||||||
|
|
||||||
|
template <typename Derived>
|
||||||
|
static Result Delete(KernelCore& kernel, const char* name) {
|
||||||
|
// Find the object.
|
||||||
|
KScopedAutoObject obj = Find(kernel, name);
|
||||||
|
R_UNLESS(obj.IsNotNull(), ResultNotFound);
|
||||||
|
|
||||||
|
// Cast the object to the desired type.
|
||||||
|
Derived* derived = obj->DynamicCast<Derived*>();
|
||||||
|
R_UNLESS(derived != nullptr, ResultNotFound);
|
||||||
|
|
||||||
|
// Check that the object is closed.
|
||||||
|
R_UNLESS(derived->IsServerClosed(), ResultInvalidState);
|
||||||
|
|
||||||
|
return Delete(kernel, obj.GetPointerUnsafe(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Derived>
|
||||||
|
requires(std::derived_from<Derived, KAutoObject>)
|
||||||
|
static KScopedAutoObject<Derived> Find(KernelCore& kernel, const char* name) {
|
||||||
|
return Find(kernel, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static KScopedAutoObject<KAutoObject> FindImpl(KernelCore& kernel, const char* name);
|
||||||
|
|
||||||
|
void Initialize(KAutoObject* obj, const char* name);
|
||||||
|
|
||||||
|
bool MatchesName(const char* name) const;
|
||||||
|
KAutoObject* GetObject() const {
|
||||||
|
return m_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<char, NameLengthMax> m_name{};
|
||||||
|
KAutoObject* m_object{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class KObjectNameGlobalData {
|
||||||
|
public:
|
||||||
|
explicit KObjectNameGlobalData(KernelCore& kernel);
|
||||||
|
~KObjectNameGlobalData();
|
||||||
|
|
||||||
|
KLightLock& GetObjectListLock() {
|
||||||
|
return m_object_list_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
KObjectName::List& GetObjectList() {
|
||||||
|
return m_object_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KLightLock m_object_list_lock;
|
||||||
|
KObjectName::List m_object_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
|
@ -29,6 +29,7 @@
|
||||||
#include "core/hle/kernel/k_hardware_timer.h"
|
#include "core/hle/kernel/k_hardware_timer.h"
|
||||||
#include "core/hle/kernel/k_memory_layout.h"
|
#include "core/hle/kernel/k_memory_layout.h"
|
||||||
#include "core/hle/kernel/k_memory_manager.h"
|
#include "core/hle/kernel/k_memory_manager.h"
|
||||||
|
#include "core/hle/kernel/k_object_name.h"
|
||||||
#include "core/hle/kernel/k_page_buffer.h"
|
#include "core/hle/kernel/k_page_buffer.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/k_resource_limit.h"
|
#include "core/hle/kernel/k_resource_limit.h"
|
||||||
|
@ -84,6 +85,7 @@ struct KernelCore::Impl {
|
||||||
InitializeShutdownThreads();
|
InitializeShutdownThreads();
|
||||||
InitializePhysicalCores();
|
InitializePhysicalCores();
|
||||||
InitializePreemption(kernel);
|
InitializePreemption(kernel);
|
||||||
|
InitializeGlobalData(kernel);
|
||||||
|
|
||||||
// Initialize the Dynamic Slab Heaps.
|
// Initialize the Dynamic Slab Heaps.
|
||||||
{
|
{
|
||||||
|
@ -194,6 +196,8 @@ struct KernelCore::Impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object_name_global_data.reset();
|
||||||
|
|
||||||
// Ensure that the object list container is finalized and properly shutdown.
|
// Ensure that the object list container is finalized and properly shutdown.
|
||||||
global_object_list_container->Finalize();
|
global_object_list_container->Finalize();
|
||||||
global_object_list_container.reset();
|
global_object_list_container.reset();
|
||||||
|
@ -363,6 +367,10 @@ struct KernelCore::Impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InitializeGlobalData(KernelCore& kernel) {
|
||||||
|
object_name_global_data = std::make_unique<KObjectNameGlobalData>(kernel);
|
||||||
|
}
|
||||||
|
|
||||||
void MakeApplicationProcess(KProcess* process) {
|
void MakeApplicationProcess(KProcess* process) {
|
||||||
application_process = process;
|
application_process = process;
|
||||||
}
|
}
|
||||||
|
@ -838,6 +846,8 @@ struct KernelCore::Impl {
|
||||||
|
|
||||||
std::unique_ptr<KAutoObjectWithListContainer> global_object_list_container;
|
std::unique_ptr<KAutoObjectWithListContainer> global_object_list_container;
|
||||||
|
|
||||||
|
std::unique_ptr<KObjectNameGlobalData> object_name_global_data;
|
||||||
|
|
||||||
/// Map of named ports managed by the kernel, which can be retrieved using
|
/// Map of named ports managed by the kernel, which can be retrieved using
|
||||||
/// the ConnectToPort SVC.
|
/// the ConnectToPort SVC.
|
||||||
std::unordered_map<std::string, ServiceInterfaceFactory> service_interface_factory;
|
std::unordered_map<std::string, ServiceInterfaceFactory> service_interface_factory;
|
||||||
|
@ -1138,6 +1148,10 @@ void KernelCore::SetCurrentEmuThread(KThread* thread) {
|
||||||
impl->SetCurrentEmuThread(thread);
|
impl->SetCurrentEmuThread(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KObjectNameGlobalData& KernelCore::ObjectNameGlobalData() {
|
||||||
|
return *impl->object_name_global_data;
|
||||||
|
}
|
||||||
|
|
||||||
KMemoryManager& KernelCore::MemoryManager() {
|
KMemoryManager& KernelCore::MemoryManager() {
|
||||||
return *impl->memory_manager;
|
return *impl->memory_manager;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ class KHardwareTimer;
|
||||||
class KLinkedListNode;
|
class KLinkedListNode;
|
||||||
class KMemoryLayout;
|
class KMemoryLayout;
|
||||||
class KMemoryManager;
|
class KMemoryManager;
|
||||||
|
class KObjectName;
|
||||||
|
class KObjectNameGlobalData;
|
||||||
class KPageBuffer;
|
class KPageBuffer;
|
||||||
class KPageBufferSlabHeap;
|
class KPageBufferSlabHeap;
|
||||||
class KPort;
|
class KPort;
|
||||||
|
@ -240,6 +242,9 @@ public:
|
||||||
/// Register the current thread as a non CPU core thread.
|
/// Register the current thread as a non CPU core thread.
|
||||||
void RegisterHostThread(KThread* existing_thread = nullptr);
|
void RegisterHostThread(KThread* existing_thread = nullptr);
|
||||||
|
|
||||||
|
/// Gets global data for KObjectName.
|
||||||
|
KObjectNameGlobalData& ObjectNameGlobalData();
|
||||||
|
|
||||||
/// Gets the virtual memory manager for the kernel.
|
/// Gets the virtual memory manager for the kernel.
|
||||||
KMemoryManager& MemoryManager();
|
KMemoryManager& MemoryManager();
|
||||||
|
|
||||||
|
@ -372,6 +377,8 @@ public:
|
||||||
return slab_heap_container->page_buffer;
|
return slab_heap_container->page_buffer;
|
||||||
} else if constexpr (std::is_same_v<T, KThreadLocalPage>) {
|
} else if constexpr (std::is_same_v<T, KThreadLocalPage>) {
|
||||||
return slab_heap_container->thread_local_page;
|
return slab_heap_container->thread_local_page;
|
||||||
|
} else if constexpr (std::is_same_v<T, KObjectName>) {
|
||||||
|
return slab_heap_container->object_name;
|
||||||
} else if constexpr (std::is_same_v<T, KSessionRequest>) {
|
} else if constexpr (std::is_same_v<T, KSessionRequest>) {
|
||||||
return slab_heap_container->session_request;
|
return slab_heap_container->session_request;
|
||||||
} else if constexpr (std::is_same_v<T, KSecureSystemResource>) {
|
} else if constexpr (std::is_same_v<T, KSecureSystemResource>) {
|
||||||
|
@ -443,6 +450,7 @@ private:
|
||||||
KSlabHeap<KDeviceAddressSpace> device_address_space;
|
KSlabHeap<KDeviceAddressSpace> device_address_space;
|
||||||
KSlabHeap<KPageBuffer> page_buffer;
|
KSlabHeap<KPageBuffer> page_buffer;
|
||||||
KSlabHeap<KThreadLocalPage> thread_local_page;
|
KSlabHeap<KThreadLocalPage> thread_local_page;
|
||||||
|
KSlabHeap<KObjectName> object_name;
|
||||||
KSlabHeap<KSessionRequest> session_request;
|
KSlabHeap<KSessionRequest> session_request;
|
||||||
KSlabHeap<KSecureSystemResource> secure_system_resource;
|
KSlabHeap<KSecureSystemResource> secure_system_resource;
|
||||||
KSlabHeap<KEventInfo> event_info;
|
KSlabHeap<KEventInfo> event_info;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/k_client_port.h"
|
#include "core/hle/kernel/k_client_port.h"
|
||||||
#include "core/hle/kernel/k_client_session.h"
|
#include "core/hle/kernel/k_client_session.h"
|
||||||
|
#include "core/hle/kernel/k_object_name.h"
|
||||||
#include "core/hle/kernel/k_port.h"
|
#include "core/hle/kernel/k_port.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/svc.h"
|
#include "core/hle/kernel/svc.h"
|
||||||
|
@ -74,10 +75,57 @@ Result ConnectToPort(Core::System& system, Handle* out_handle, Handle port) {
|
||||||
R_THROW(ResultNotImplemented);
|
R_THROW(ResultNotImplemented);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ManageNamedPort(Core::System& system, Handle* out_server_handle, uint64_t name,
|
Result ManageNamedPort(Core::System& system, Handle* out_server_handle, uint64_t user_name,
|
||||||
int32_t max_sessions) {
|
int32_t max_sessions) {
|
||||||
UNIMPLEMENTED();
|
// Copy the provided name from user memory to kernel memory.
|
||||||
R_THROW(ResultNotImplemented);
|
std::array<char, KObjectName::NameLengthMax> name{};
|
||||||
|
system.Memory().ReadBlock(user_name, name.data(), sizeof(name));
|
||||||
|
|
||||||
|
// Validate that sessions and name are valid.
|
||||||
|
R_UNLESS(max_sessions >= 0, ResultOutOfRange);
|
||||||
|
R_UNLESS(name[sizeof(name) - 1] == '\x00', ResultOutOfRange);
|
||||||
|
|
||||||
|
if (max_sessions > 0) {
|
||||||
|
// Get the current handle table.
|
||||||
|
auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
|
||||||
|
|
||||||
|
// Create a new port.
|
||||||
|
KPort* port = KPort::Create(system.Kernel());
|
||||||
|
R_UNLESS(port != nullptr, ResultOutOfResource);
|
||||||
|
|
||||||
|
// Initialize the new port.
|
||||||
|
port->Initialize(max_sessions, false, "");
|
||||||
|
|
||||||
|
// Register the port.
|
||||||
|
KPort::Register(system.Kernel(), port);
|
||||||
|
|
||||||
|
// Ensure that our only reference to the port is in the handle table when we're done.
|
||||||
|
SCOPE_EXIT({
|
||||||
|
port->GetClientPort().Close();
|
||||||
|
port->GetServerPort().Close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register the handle in the table.
|
||||||
|
R_TRY(handle_table.Add(out_server_handle, std::addressof(port->GetServerPort())));
|
||||||
|
ON_RESULT_FAILURE {
|
||||||
|
handle_table.Remove(*out_server_handle);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new object name.
|
||||||
|
R_TRY(KObjectName::NewFromName(system.Kernel(), std::addressof(port->GetClientPort()),
|
||||||
|
name.data()));
|
||||||
|
} else /* if (max_sessions == 0) */ {
|
||||||
|
// Ensure that this else case is correct.
|
||||||
|
ASSERT(max_sessions == 0);
|
||||||
|
|
||||||
|
// If we're closing, there's no server handle.
|
||||||
|
*out_server_handle = InvalidHandle;
|
||||||
|
|
||||||
|
// Delete the object.
|
||||||
|
R_TRY(KObjectName::Delete<KClientPort>(system.Kernel(), name.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ConnectToNamedPort64(Core::System& system, Handle* out_handle, uint64_t name) {
|
Result ConnectToNamedPort64(Core::System& system, Handle* out_handle, uint64_t name) {
|
||||||
|
|
Loading…
Reference in a new issue