mirror of
https://github.com/PabloMK7/citra.git
synced 2025-09-10 12:50:04 +00:00
misc: fix issues pointed out by msvc (#7316)
* do not move constant variables
* applet_manager: avoid possible use after move
* use constant references where pointed out by msvc
* extra_hid: initialize response
* ValidateSaveState: passing slot separately is not necessary
* common: mark HashCombine as nodiscard
* cityhash: remove use of using namespace std
* Prefix all size_t with std::
done automatically by executing regex replace `([^:0-9a-zA-Z_])size_t([^0-9a-zA-Z_])` -> `$1std::size_t$2`
based on 7d8f115
* shared_memory.cpp: fix log error format
* fix compiling with pch off
This commit is contained in:
parent
6069fac76d
commit
c8c2beaeff
73 changed files with 181 additions and 167 deletions
|
@ -13,8 +13,8 @@ namespace {
|
|||
template <Common::StringLiteral haystack, Common::StringLiteral needle>
|
||||
constexpr u32 GetMatchingBitsFromStringLiteral() {
|
||||
u32 result = 0;
|
||||
for (size_t i = 0; i < haystack.strlen; i++) {
|
||||
for (size_t a = 0; a < needle.strlen; a++) {
|
||||
for (std::size_t i = 0; i < haystack.strlen; i++) {
|
||||
for (std::size_t a = 0; a < needle.strlen; a++) {
|
||||
if (haystack.value[i] == needle.value[a]) {
|
||||
result |= 1 << (haystack.strlen - 1 - i);
|
||||
}
|
||||
|
|
|
@ -1073,7 +1073,7 @@ std::vector<FormatInfo> ListFormats() {
|
|||
continue;
|
||||
}
|
||||
|
||||
out.push_back({current->name, ToStdString(current->long_name), std::move(extensions),
|
||||
out.push_back({current->name, ToStdString(current->long_name), extensions,
|
||||
std::move(supported_video_codecs), std::move(supported_audio_codecs),
|
||||
GetOptionList(current->priv_class, true)});
|
||||
}
|
||||
|
|
|
@ -57,9 +57,9 @@ static bool LZSS_Decompress(std::span<const u8> compressed, std::span<u8> decomp
|
|||
u32 buffer_top_and_bottom;
|
||||
std::memcpy(&buffer_top_and_bottom, footer, sizeof(u32));
|
||||
|
||||
size_t out = decompressed.size();
|
||||
size_t index = compressed.size() - ((buffer_top_and_bottom >> 24) & 0xFF);
|
||||
size_t stop_index = compressed.size() - (buffer_top_and_bottom & 0xFFFFFF);
|
||||
std::size_t out = decompressed.size();
|
||||
std::size_t index = compressed.size() - ((buffer_top_and_bottom >> 24) & 0xFF);
|
||||
std::size_t stop_index = compressed.size() - (buffer_top_and_bottom & 0xFFFFFF);
|
||||
|
||||
std::memset(decompressed.data(), 0, decompressed.size());
|
||||
std::memcpy(decompressed.data(), compressed.data(), compressed.size());
|
||||
|
|
|
@ -131,7 +131,7 @@ public:
|
|||
return m_offset;
|
||||
}
|
||||
|
||||
bool Seek(size_t offset) {
|
||||
bool Seek(std::size_t offset) {
|
||||
if (offset > m_size)
|
||||
return false;
|
||||
m_offset = offset;
|
||||
|
@ -242,7 +242,7 @@ private:
|
|||
if (m_target_relative_offset + length > m_target.size())
|
||||
return false;
|
||||
// Byte by byte copy.
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
m_target.data()[m_target.Tell() + i] = m_target.data()[m_target_relative_offset++];
|
||||
m_target.Seek(m_target.Tell() + length);
|
||||
return true;
|
||||
|
|
|
@ -357,7 +357,7 @@ void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::K
|
|||
// Write bootloader
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr - bootloader_memory_size, bootloader.data(),
|
||||
std::min<size_t>(bootloader.size() * sizeof(u32), bootloader_memory_size));
|
||||
std::min<std::size_t>(bootloader.size() * sizeof(u32), bootloader_memory_size));
|
||||
|
||||
game_instructions[0] = 0xE51FF004; // ldr pc, [pc, #-4]
|
||||
game_instructions[1] = _3GX_exe_load_addr - bootloader_memory_size;
|
||||
|
|
|
@ -72,7 +72,7 @@ private:
|
|||
Kernel::Process& process, Kernel::KernelSystem& kernel,
|
||||
Service::PLGLDR::PLG_LDR& plg_ldr);
|
||||
|
||||
static constexpr size_t bootloader_memory_size = 0x1000;
|
||||
static constexpr std::size_t bootloader_memory_size = 0x1000;
|
||||
static void MapBootloader(Kernel::Process& process, Kernel::KernelSystem& kernel,
|
||||
u32 memory_offset, std::span<const u32> exe_load_func,
|
||||
const u32_le* exe_load_args, u32 checksum_size, u32 exe_checksum,
|
||||
|
|
|
@ -16,7 +16,7 @@ std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length,
|
|||
return 0; // Crypto++ does not like zero size buffer
|
||||
|
||||
const auto segments = BreakupRead(offset, length);
|
||||
size_t read_progress = 0;
|
||||
std::size_t read_progress = 0;
|
||||
|
||||
// Skip cache if the read is too big
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
|
@ -33,8 +33,8 @@ std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length,
|
|||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
||||
// std::unique_lock<std::shared_mutex> read_guard(cache_mutex);
|
||||
for (const auto& seg : segments) {
|
||||
size_t read_size = cache_line_size;
|
||||
size_t page = OffsetToPage(seg.first);
|
||||
std::size_t read_size = cache_line_size;
|
||||
std::size_t page = OffsetToPage(seg.first);
|
||||
// Check if segment is in cache
|
||||
auto cache_entry = cache.request(page);
|
||||
if (!cache_entry.first) {
|
||||
|
@ -51,7 +51,7 @@ std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length,
|
|||
LOG_TRACE(Service_FS, "RomFS Cache HIT: page={}, length={}, into={}", page, seg.second,
|
||||
(seg.first - page));
|
||||
}
|
||||
size_t copy_amount =
|
||||
std::size_t copy_amount =
|
||||
(read_size > (seg.first - page))
|
||||
? std::min((seg.first - page) + seg.second, read_size) - (seg.first - page)
|
||||
: 0;
|
||||
|
@ -98,10 +98,10 @@ std::vector<std::pair<std::size_t, std::size_t>> DirectRomFSReader::BreakupRead(
|
|||
return ret;
|
||||
}
|
||||
|
||||
size_t curr_offset = offset;
|
||||
std::size_t curr_offset = offset;
|
||||
while (length) {
|
||||
size_t next_page = OffsetToPage(curr_offset + cache_line_size);
|
||||
size_t curr_page_len = std::min(length, next_page - curr_offset);
|
||||
std::size_t next_page = OffsetToPage(curr_offset + cache_line_size);
|
||||
std::size_t curr_page_len = std::min(length, next_page - curr_offset);
|
||||
ret.push_back(std::make_pair(curr_offset, curr_page_len));
|
||||
curr_offset = next_page;
|
||||
length -= curr_page_len;
|
||||
|
|
|
@ -67,8 +67,8 @@ private:
|
|||
u64 data_size;
|
||||
|
||||
// Total cache size: 128KB
|
||||
static constexpr size_t cache_line_size = (1 << 13); // About 8KB
|
||||
static constexpr size_t cache_line_count = 16;
|
||||
static constexpr std::size_t cache_line_size = (1 << 13); // About 8KB
|
||||
static constexpr std::size_t cache_line_count = 16;
|
||||
|
||||
Common::StaticLRUCache<std::size_t, std::array<u8, cache_line_size>, cache_line_count> cache;
|
||||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
||||
|
|
|
@ -22,22 +22,22 @@ std::shared_ptr<ResourceLimit> ResourceLimit::Create(KernelSystem& kernel, std::
|
|||
}
|
||||
|
||||
s32 ResourceLimit::GetCurrentValue(ResourceLimitType type) const {
|
||||
const auto index = static_cast<size_t>(type);
|
||||
const auto index = static_cast<std::size_t>(type);
|
||||
return m_current_values[index];
|
||||
}
|
||||
|
||||
s32 ResourceLimit::GetLimitValue(ResourceLimitType type) const {
|
||||
const auto index = static_cast<size_t>(type);
|
||||
const auto index = static_cast<std::size_t>(type);
|
||||
return m_limit_values[index];
|
||||
}
|
||||
|
||||
void ResourceLimit::SetLimitValue(ResourceLimitType type, s32 value) {
|
||||
const auto index = static_cast<size_t>(type);
|
||||
const auto index = static_cast<std::size_t>(type);
|
||||
m_limit_values[index] = value;
|
||||
}
|
||||
|
||||
bool ResourceLimit::Reserve(ResourceLimitType type, s32 amount) {
|
||||
const auto index = static_cast<size_t>(type);
|
||||
const auto index = static_cast<std::size_t>(type);
|
||||
const s32 limit = m_limit_values[index];
|
||||
const s32 new_value = m_current_values[index] + amount;
|
||||
if (new_value > limit) {
|
||||
|
@ -50,7 +50,7 @@ bool ResourceLimit::Reserve(ResourceLimitType type, s32 amount) {
|
|||
}
|
||||
|
||||
bool ResourceLimit::Release(ResourceLimitType type, s32 amount) {
|
||||
const auto index = static_cast<size_t>(type);
|
||||
const auto index = static_cast<std::size_t>(type);
|
||||
const s32 value = m_current_values[index];
|
||||
if (amount > value) {
|
||||
LOG_ERROR(Kernel, "Amount {} exceeds current value {} for resource type {}", amount, value,
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
bool Release(ResourceLimitType type, s32 amount);
|
||||
|
||||
private:
|
||||
using ResourceArray = std::array<s32, static_cast<size_t>(ResourceLimitType::Max)>;
|
||||
using ResourceArray = std::array<s32, static_cast<std::size_t>(ResourceLimitType::Max)>;
|
||||
ResourceArray m_limit_values{};
|
||||
ResourceArray m_current_values{};
|
||||
std::string m_name;
|
||||
|
|
|
@ -127,7 +127,7 @@ Result SharedMemory::Map(Process& target_process, VAddr address, MemoryPermissio
|
|||
|
||||
// Heap-backed memory blocks can not be mapped with other_permissions = DontCare
|
||||
if (base_address != 0 && other_permissions == MemoryPermission::DontCare) {
|
||||
LOG_ERROR(Kernel, "cannot map id={}, address=0x{08X} name={}, permissions don't match",
|
||||
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:08X} name={}, permissions don't match",
|
||||
GetObjectId(), address, name);
|
||||
return ResultInvalidCombination;
|
||||
}
|
||||
|
|
|
@ -1686,8 +1686,8 @@ Result SVC::AcceptSession(Handle* out_server_session, Handle server_port_handle)
|
|||
return current_process->handle_table.Create(out_server_session, std::move(session));
|
||||
}
|
||||
|
||||
static void CopyStringPart(char* out, const char* in, size_t offset, size_t max_length) {
|
||||
size_t str_size = strlen(in);
|
||||
static void CopyStringPart(char* out, const char* in, std::size_t offset, std::size_t max_length) {
|
||||
std::size_t str_size = strlen(in);
|
||||
if (offset < str_size) {
|
||||
strncpy(out, in + offset, max_length - 1);
|
||||
out[max_length - 1] = '\0';
|
||||
|
|
|
@ -244,7 +244,7 @@ void Thread::WakeAfterDelay(s64 nanoseconds, bool thread_safe_mode) {
|
|||
// Don't schedule a wakeup if the thread wants to wait forever
|
||||
if (nanoseconds == -1)
|
||||
return;
|
||||
size_t core = thread_safe_mode ? core_id : std::numeric_limits<std::size_t>::max();
|
||||
std::size_t core = thread_safe_mode ? core_id : std::numeric_limits<std::size_t>::max();
|
||||
|
||||
thread_manager.kernel.timing.ScheduleEvent(nsToCycles(nanoseconds),
|
||||
thread_manager.ThreadWakeupEventType, thread_id,
|
||||
|
|
|
@ -713,6 +713,7 @@ Result AppletManager::SendDspSleep(AppletId from_applet_id,
|
|||
.signal = SignalType::DspSleep,
|
||||
.object = std::move(object),
|
||||
});
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
auto sys_lib_slot = GetAppletSlotFromPos(AppletPos::SysLibrary);
|
||||
|
@ -728,6 +729,7 @@ Result AppletManager::SendDspSleep(AppletId from_applet_id,
|
|||
.signal = SignalType::DspSleep,
|
||||
.object = std::move(object),
|
||||
});
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
|
|
|
@ -399,8 +399,8 @@ Result OnlineService::GetNsDataHeaderInfo(const u32 ns_data_id, const NsDataHead
|
|||
}
|
||||
}
|
||||
|
||||
ResultVal<size_t> OnlineService::ReadNsData(const u32 ns_data_id, const u64 offset, const u32 size,
|
||||
Kernel::MappedBuffer& buffer) {
|
||||
ResultVal<std::size_t> OnlineService::ReadNsData(const u32 ns_data_id, const u64 offset,
|
||||
const u32 size, Kernel::MappedBuffer& buffer) {
|
||||
std::optional<NsDataEntry> entry = GetNsDataEntryFromId(ns_data_id);
|
||||
if (!entry.has_value()) {
|
||||
LOG_WARNING(Service_BOSS, "Failed to find NsData entry for ID {:#010X}", ns_data_id);
|
||||
|
|
|
@ -36,11 +36,11 @@ constexpr u32 BOSS_EXTDATA_HEADER_LENGTH = 0x18;
|
|||
constexpr u32 BOSS_S_ENTRY_SIZE = 0xC00;
|
||||
constexpr u32 BOSS_SAVE_HEADER_SIZE = 4;
|
||||
|
||||
constexpr size_t TASK_ID_SIZE = 8;
|
||||
constexpr size_t URL_SIZE = 0x200;
|
||||
constexpr size_t HEADERS_SIZE = 0x360;
|
||||
constexpr size_t CERTIDLIST_SIZE = 3;
|
||||
constexpr size_t TASKIDLIST_SIZE = 0x400;
|
||||
constexpr std::size_t TASK_ID_SIZE = 8;
|
||||
constexpr std::size_t URL_SIZE = 0x200;
|
||||
constexpr std::size_t HEADERS_SIZE = 0x360;
|
||||
constexpr std::size_t CERTIDLIST_SIZE = 3;
|
||||
constexpr std::size_t TASKIDLIST_SIZE = 0x400;
|
||||
|
||||
constexpr std::array<u8, 8> boss_system_savedata_id{
|
||||
0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x01, 0x00,
|
||||
|
@ -188,8 +188,8 @@ public:
|
|||
std::optional<NsDataEntry> GetNsDataEntryFromId(const u32 ns_data_id);
|
||||
Result GetNsDataHeaderInfo(const u32 ns_data_id, const NsDataHeaderInfoType type,
|
||||
const u32 size, Kernel::MappedBuffer& buffer);
|
||||
ResultVal<size_t> ReadNsData(const u32 ns_data_id, const u64 offset, const u32 size,
|
||||
Kernel::MappedBuffer& buffer);
|
||||
ResultVal<std::size_t> ReadNsData(const u32 ns_data_id, const u64 offset, const u32 size,
|
||||
Kernel::MappedBuffer& buffer);
|
||||
Result SendProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer);
|
||||
Result ReceiveProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer);
|
||||
|
||||
|
|
|
@ -649,7 +649,7 @@ void Module::Interface::ReadData(Kernel::HLERequestContext& ctx) {
|
|||
buffer = {};
|
||||
}
|
||||
dest_buffer.Write(buffer.data(), 0,
|
||||
std::min(static_cast<size_t>(dest_buffer_size), buffer.size()));
|
||||
std::min(static_cast<std::size_t>(dest_buffer_size), buffer.size()));
|
||||
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushMappedBuffer(param_buffer);
|
||||
|
|
|
@ -384,7 +384,7 @@ ResultVal<void*> Module::GetConfigBlockPointer(u32 block_id, u32 size, AccessFla
|
|||
"Config block 0x{:X} with flags {} and size {} was not found, creating "
|
||||
"from defaults.",
|
||||
block_id, accesss_flag, size);
|
||||
auto default_block = GetDefaultConfigBlock(static_cast<ConfigBlockID>(block_id));
|
||||
const auto& default_block = GetDefaultConfigBlock(static_cast<ConfigBlockID>(block_id));
|
||||
auto result = CreateConfigBlock(block_id, static_cast<u16>(default_block.data.size()),
|
||||
default_block.access_flags, default_block.data.data());
|
||||
if (!result.IsSuccess()) {
|
||||
|
|
|
@ -107,7 +107,7 @@ void File::Read(Kernel::HLERequestContext& ctx) {
|
|||
Result ret{0};
|
||||
Kernel::MappedBuffer* buffer;
|
||||
std::unique_ptr<u8*> data;
|
||||
size_t read_size;
|
||||
std::size_t read_size;
|
||||
};
|
||||
|
||||
auto async_data = std::make_shared<AsyncData>();
|
||||
|
|
|
@ -112,9 +112,9 @@ static URLInfo SplitUrl(const std::string& url) {
|
|||
};
|
||||
}
|
||||
|
||||
static size_t WriteHeaders(httplib::Stream& stream,
|
||||
std::span<const Context::RequestHeader> headers) {
|
||||
size_t write_len = 0;
|
||||
static std::size_t WriteHeaders(httplib::Stream& stream,
|
||||
std::span<const Context::RequestHeader> headers) {
|
||||
std::size_t write_len = 0;
|
||||
for (const auto& header : headers) {
|
||||
auto len = stream.write_format("%s: %s\r\n", header.name.c_str(), header.value.c_str());
|
||||
if (len < 0) {
|
||||
|
@ -130,8 +130,8 @@ static size_t WriteHeaders(httplib::Stream& stream,
|
|||
return write_len;
|
||||
}
|
||||
|
||||
static size_t HandleHeaderWrite(std::vector<Context::RequestHeader>& pending_headers,
|
||||
httplib::Stream& strm, httplib::Headers& httplib_headers) {
|
||||
static std::size_t HandleHeaderWrite(std::vector<Context::RequestHeader>& pending_headers,
|
||||
httplib::Stream& strm, httplib::Headers& httplib_headers) {
|
||||
std::vector<Context::RequestHeader> final_headers;
|
||||
std::vector<Context::RequestHeader>::iterator it_pending_headers;
|
||||
httplib::Headers::iterator it_httplib_headers;
|
||||
|
@ -508,7 +508,7 @@ void HTTP_C::ReceiveDataImpl(Kernel::HLERequestContext& ctx, bool timeout) {
|
|||
}
|
||||
Context& http_context = GetContext(async_data->context_handle);
|
||||
|
||||
const size_t remaining_data =
|
||||
const std::size_t remaining_data =
|
||||
http_context.response.body.size() - http_context.current_copied_data;
|
||||
|
||||
if (async_data->buffer_size >= remaining_data) {
|
||||
|
|
|
@ -217,7 +217,7 @@ public:
|
|||
std::future<void> request_future;
|
||||
std::atomic<u64> current_download_size_bytes;
|
||||
std::atomic<u64> total_download_size_bytes;
|
||||
size_t current_copied_data;
|
||||
std::size_t current_copied_data;
|
||||
bool uses_default_client_cert{};
|
||||
httplib::Response response;
|
||||
|
||||
|
|
|
@ -238,7 +238,7 @@ void ExtraHID::SendHIDStatus() {
|
|||
float x, y;
|
||||
std::tie(x, y) = c_stick->GetStatus();
|
||||
|
||||
ExtraHIDResponse response;
|
||||
ExtraHIDResponse response{};
|
||||
response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
|
||||
response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
|
||||
response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
|
||||
|
|
|
@ -1102,7 +1102,7 @@ void SOC_U::SendToOther(Kernel::HLERequestContext& ctx) {
|
|||
#endif // _WIN32
|
||||
std::vector<u8> input_buff(len);
|
||||
input_mapped_buff.Read(input_buff.data(), 0,
|
||||
std::min(input_mapped_buff.GetSize(), static_cast<size_t>(len)));
|
||||
std::min(input_mapped_buff.GetSize(), static_cast<std::size_t>(len)));
|
||||
|
||||
s32 ret = -1;
|
||||
if (addr_len > 0) {
|
||||
|
@ -1853,7 +1853,7 @@ void SOC_U::GetSockOpt(Kernel::HLERequestContext& ctx) {
|
|||
if (err == SOCKET_ERROR_VALUE) {
|
||||
err = TranslateError(GET_ERRNO);
|
||||
} else {
|
||||
platform_data.resize(static_cast<size_t>(platform_data_size));
|
||||
platform_data.resize(static_cast<std::size_t>(platform_data_size));
|
||||
TranslateSockOptDataFromPlatform(optval, platform_data, level_opt.first,
|
||||
level_opt.second);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ class AppLoader_THREEDSX final : public AppLoader {
|
|||
public:
|
||||
AppLoader_THREEDSX(Core::System& system_, FileUtil::IOFile&& file, const std::string& filename,
|
||||
const std::string& filepath)
|
||||
: AppLoader(system_, std::move(file)), filename(std::move(filename)), filepath(filepath) {}
|
||||
: AppLoader(system_, std::move(file)), filename(filename), filepath(filepath) {}
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
|
|
|
@ -48,8 +48,8 @@ static std::string GetSaveStatePath(u64 program_id, u64 movie_id, u32 slot) {
|
|||
}
|
||||
|
||||
static bool ValidateSaveState(const CSTHeader& header, SaveStateInfo& info, u64 program_id,
|
||||
u64 movie_id, u32 slot) {
|
||||
const auto path = GetSaveStatePath(program_id, movie_id, slot);
|
||||
u64 movie_id) {
|
||||
const auto path = GetSaveStatePath(program_id, movie_id, info.slot);
|
||||
if (header.filetype != header_magic_bytes) {
|
||||
LOG_WARNING(Core, "Invalid save state file {}", path);
|
||||
return false;
|
||||
|
@ -112,7 +112,7 @@ std::vector<SaveStateInfo> ListSaveStates(u64 program_id, u64 movie_id) {
|
|||
LOG_ERROR(Core, "Could not read from file {}", path);
|
||||
continue;
|
||||
}
|
||||
if (!ValidateSaveState(header, info, program_id, movie_id, slot)) {
|
||||
if (!ValidateSaveState(header, info, program_id, movie_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,8 @@ void System::LoadState(u32 slot) {
|
|||
|
||||
// validate header
|
||||
SaveStateInfo info;
|
||||
if (!ValidateSaveState(header, info, title_id, movie_id, slot)) {
|
||||
info.slot = slot;
|
||||
if (!ValidateSaveState(header, info, title_id, movie_id)) {
|
||||
throw std::runtime_error("Invalid savestate");
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue