mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Changed u8* to MemoryRef
This commit is contained in:
		
							parent
							
								
									cf985631e0
								
							
						
					
					
						commit
						65d96bf6c1
					
				
					 24 changed files with 486 additions and 177 deletions
				
			
		|  | @ -22,8 +22,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_) | |||
|     kernel->SetCurrentProcess(kernel->CreateProcess(kernel->CreateCodeSet("", 0))); | ||||
|     page_table = &kernel->GetCurrentProcess()->vm_manager.page_table; | ||||
| 
 | ||||
|     page_table->pointers.fill(nullptr); | ||||
|     page_table->attributes.fill(Memory::PageType::Unmapped); | ||||
|     page_table->Clear(); | ||||
| 
 | ||||
|     memory->MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory); | ||||
|     memory->MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory); | ||||
|  |  | |||
|  | @ -138,67 +138,70 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel | |||
|     } | ||||
| 
 | ||||
|     SECTION("translates StaticBuffer descriptors") { | ||||
|         auto buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
|         std::fill(buffer->begin(), buffer->end(), 0xAB); | ||||
|         auto mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE); | ||||
|         MemoryRef buffer{mem}; | ||||
|         std::fill(buffer.GetPtr(), buffer.GetPtr() + buffer.GetSize(), 0xAB); | ||||
| 
 | ||||
|         VAddr target_address = 0x10000000; | ||||
|         auto result = process->vm_manager.MapBackingMemory(target_address, buffer->data(), | ||||
|                                                            buffer->size(), MemoryState::Private); | ||||
|         auto result = process->vm_manager.MapBackingMemory(target_address, buffer, buffer.GetSize(), | ||||
|                                                            MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         const u32_le input[]{ | ||||
|             IPC::MakeHeader(0, 0, 2), | ||||
|             IPC::StaticBufferDesc(buffer->size(), 0), | ||||
|             IPC::StaticBufferDesc(buffer.GetSize(), 0), | ||||
|             target_address, | ||||
|         }; | ||||
| 
 | ||||
|         context.PopulateFromIncomingCommandBuffer(input, *process); | ||||
| 
 | ||||
|         CHECK(context.GetStaticBuffer(0) == *buffer); | ||||
|         CHECK(context.GetStaticBuffer(0) == mem->Vector()); | ||||
| 
 | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address, buffer->size()) == RESULT_SUCCESS); | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address, buffer.GetSize()) == RESULT_SUCCESS); | ||||
|     } | ||||
| 
 | ||||
|     SECTION("translates MappedBuffer descriptors") { | ||||
|         auto buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
|         std::fill(buffer->begin(), buffer->end(), 0xCD); | ||||
|         auto mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE); | ||||
|         MemoryRef buffer{mem}; | ||||
|         std::fill(buffer.GetPtr(), buffer.GetPtr() + buffer.GetSize(), 0xCD); | ||||
| 
 | ||||
|         VAddr target_address = 0x10000000; | ||||
|         auto result = process->vm_manager.MapBackingMemory(target_address, buffer->data(), | ||||
|                                                            buffer->size(), MemoryState::Private); | ||||
|         auto result = process->vm_manager.MapBackingMemory(target_address, buffer, buffer.GetSize(), | ||||
|                                                            MemoryState::Private); | ||||
| 
 | ||||
|         const u32_le input[]{ | ||||
|             IPC::MakeHeader(0, 0, 2), | ||||
|             IPC::MappedBufferDesc(buffer->size(), IPC::R), | ||||
|             IPC::MappedBufferDesc(buffer.GetSize(), IPC::R), | ||||
|             target_address, | ||||
|         }; | ||||
| 
 | ||||
|         context.PopulateFromIncomingCommandBuffer(input, *process); | ||||
| 
 | ||||
|         std::vector<u8> other_buffer(buffer->size()); | ||||
|         context.GetMappedBuffer(0).Read(other_buffer.data(), 0, buffer->size()); | ||||
|         std::vector<u8> other_buffer(buffer.GetSize()); | ||||
|         context.GetMappedBuffer(0).Read(other_buffer.data(), 0, buffer.GetSize()); | ||||
| 
 | ||||
|         CHECK(other_buffer == *buffer); | ||||
|         CHECK(other_buffer == mem->Vector()); | ||||
| 
 | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address, buffer->size()) == RESULT_SUCCESS); | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address, buffer.GetSize()) == RESULT_SUCCESS); | ||||
|     } | ||||
| 
 | ||||
|     SECTION("translates mixed params") { | ||||
|         auto buffer_static = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
|         std::fill(buffer_static->begin(), buffer_static->end(), 0xCE); | ||||
|         auto mem_static = std::make_shared<BufferMem>(Memory::PAGE_SIZE); | ||||
|         MemoryRef buffer_static{mem_static}; | ||||
|         std::fill(buffer_static.GetPtr(), buffer_static.GetPtr() + buffer_static.GetSize(), 0xCE); | ||||
| 
 | ||||
|         auto buffer_mapped = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
|         std::fill(buffer_mapped->begin(), buffer_mapped->end(), 0xDF); | ||||
|         auto mem_mapped = std::make_shared<BufferMem>(Memory::PAGE_SIZE); | ||||
|         MemoryRef buffer_mapped{mem_mapped}; | ||||
|         std::fill(buffer_mapped.GetPtr(), buffer_mapped.GetPtr() + buffer_mapped.GetSize(), 0xDF); | ||||
| 
 | ||||
|         VAddr target_address_static = 0x10000000; | ||||
|         auto result = | ||||
|             process->vm_manager.MapBackingMemory(target_address_static, buffer_static->data(), | ||||
|                                                  buffer_static->size(), MemoryState::Private); | ||||
|         auto result = process->vm_manager.MapBackingMemory( | ||||
|             target_address_static, buffer_static, buffer_static.GetSize(), MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         VAddr target_address_mapped = 0x20000000; | ||||
|         result = process->vm_manager.MapBackingMemory(target_address_mapped, buffer_mapped->data(), | ||||
|                                                       buffer_mapped->size(), MemoryState::Private); | ||||
|         result = process->vm_manager.MapBackingMemory( | ||||
|             target_address_mapped, buffer_mapped, buffer_mapped.GetSize(), MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         auto a = MakeObject(kernel); | ||||
|  | @ -210,9 +213,9 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel | |||
|             process->handle_table.Create(a).Unwrap(), | ||||
|             IPC::CallingPidDesc(), | ||||
|             0, | ||||
|             IPC::StaticBufferDesc(buffer_static->size(), 0), | ||||
|             IPC::StaticBufferDesc(buffer_static.GetSize(), 0), | ||||
|             target_address_static, | ||||
|             IPC::MappedBufferDesc(buffer_mapped->size(), IPC::R), | ||||
|             IPC::MappedBufferDesc(buffer_mapped.GetSize(), IPC::R), | ||||
|             target_address_mapped, | ||||
|         }; | ||||
| 
 | ||||
|  | @ -223,14 +226,14 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel | |||
|         CHECK(output[2] == 0xABCDEF00); | ||||
|         CHECK(context.GetIncomingHandle(output[4]) == a); | ||||
|         CHECK(output[6] == process->process_id); | ||||
|         CHECK(context.GetStaticBuffer(0) == *buffer_static); | ||||
|         std::vector<u8> other_buffer(buffer_mapped->size()); | ||||
|         context.GetMappedBuffer(0).Read(other_buffer.data(), 0, buffer_mapped->size()); | ||||
|         CHECK(other_buffer == *buffer_mapped); | ||||
|         CHECK(context.GetStaticBuffer(0) == mem_static->Vector()); | ||||
|         std::vector<u8> other_buffer(buffer_mapped.GetSize()); | ||||
|         context.GetMappedBuffer(0).Read(other_buffer.data(), 0, buffer_mapped.GetSize()); | ||||
|         CHECK(other_buffer == mem_mapped->Vector()); | ||||
| 
 | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address_static, buffer_static->size()) == | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address_static, buffer_static.GetSize()) == | ||||
|                 RESULT_SUCCESS); | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address_mapped, buffer_mapped->size()) == | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address_mapped, buffer_mapped.GetSize()) == | ||||
|                 RESULT_SUCCESS); | ||||
|     } | ||||
| } | ||||
|  | @ -317,10 +320,12 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { | |||
| 
 | ||||
|         context.AddStaticBuffer(0, input_buffer); | ||||
| 
 | ||||
|         auto output_buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
|         auto output_mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE); | ||||
|         MemoryRef output_buffer{output_mem}; | ||||
| 
 | ||||
|         VAddr target_address = 0x10000000; | ||||
|         auto result = process->vm_manager.MapBackingMemory( | ||||
|             target_address, output_buffer->data(), output_buffer->size(), MemoryState::Private); | ||||
|             target_address, output_buffer, output_buffer.GetSize(), MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         input[0] = IPC::MakeHeader(0, 0, 2); | ||||
|  | @ -332,13 +337,13 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { | |||
|         std::array<u32_le, IPC::COMMAND_BUFFER_LENGTH + 2> output_cmdbuff; | ||||
|         // Set up the output StaticBuffer
 | ||||
|         output_cmdbuff[IPC::COMMAND_BUFFER_LENGTH] = | ||||
|             IPC::StaticBufferDesc(output_buffer->size(), 0); | ||||
|             IPC::StaticBufferDesc(output_buffer.GetSize(), 0); | ||||
|         output_cmdbuff[IPC::COMMAND_BUFFER_LENGTH + 1] = target_address; | ||||
| 
 | ||||
|         context.WriteToOutgoingCommandBuffer(output_cmdbuff.data(), *process); | ||||
| 
 | ||||
|         CHECK(*output_buffer == input_buffer); | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address, output_buffer->size()) == | ||||
|         CHECK(output_mem->Vector() == input_buffer); | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address, output_buffer.GetSize()) == | ||||
|                 RESULT_SUCCESS); | ||||
|     } | ||||
| 
 | ||||
|  | @ -346,15 +351,17 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { | |||
|         std::vector<u8> input_buffer(Memory::PAGE_SIZE); | ||||
|         std::fill(input_buffer.begin(), input_buffer.end(), 0xAB); | ||||
| 
 | ||||
|         auto output_buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
|         auto output_mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE); | ||||
|         MemoryRef output_buffer{output_mem}; | ||||
| 
 | ||||
|         VAddr target_address = 0x10000000; | ||||
|         auto result = process->vm_manager.MapBackingMemory( | ||||
|             target_address, output_buffer->data(), output_buffer->size(), MemoryState::Private); | ||||
|             target_address, output_buffer, output_buffer.GetSize(), MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         const u32_le input_cmdbuff[]{ | ||||
|             IPC::MakeHeader(0, 0, 2), | ||||
|             IPC::MappedBufferDesc(output_buffer->size(), IPC::W), | ||||
|             IPC::MappedBufferDesc(output_buffer.GetSize(), IPC::W), | ||||
|             target_address, | ||||
|         }; | ||||
| 
 | ||||
|  | @ -363,15 +370,15 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { | |||
|         context.GetMappedBuffer(0).Write(input_buffer.data(), 0, input_buffer.size()); | ||||
| 
 | ||||
|         input[0] = IPC::MakeHeader(0, 0, 2); | ||||
|         input[1] = IPC::MappedBufferDesc(output_buffer->size(), IPC::W); | ||||
|         input[1] = IPC::MappedBufferDesc(output_buffer.GetSize(), IPC::W); | ||||
|         input[2] = 0; | ||||
| 
 | ||||
|         context.WriteToOutgoingCommandBuffer(output, *process); | ||||
| 
 | ||||
|         CHECK(output[1] == IPC::MappedBufferDesc(output_buffer->size(), IPC::W)); | ||||
|         CHECK(output[1] == IPC::MappedBufferDesc(output_buffer.GetSize(), IPC::W)); | ||||
|         CHECK(output[2] == target_address); | ||||
|         CHECK(*output_buffer == input_buffer); | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address, output_buffer->size()) == | ||||
|         CHECK(output_mem->Vector() == input_buffer); | ||||
|         REQUIRE(process->vm_manager.UnmapRange(target_address, output_buffer.GetSize()) == | ||||
|                 RESULT_SUCCESS); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -10,47 +10,48 @@ | |||
| #include "core/memory.h" | ||||
| 
 | ||||
| TEST_CASE("Memory Basics", "[kernel][memory]") { | ||||
|     auto block = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE); | ||||
|     auto mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE); | ||||
|     MemoryRef block{mem}; | ||||
|     Memory::MemorySystem memory; | ||||
|     SECTION("mapping memory") { | ||||
|         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
 | ||||
|         auto manager = std::make_unique<Kernel::VMManager>(memory); | ||||
|         auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block->data(), block->size(), | ||||
|         auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, block.GetSize(), | ||||
|                                                 Kernel::MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         auto vma = manager->FindVMA(Memory::HEAP_VADDR); | ||||
|         CHECK(vma != manager->vma_map.end()); | ||||
|         CHECK(vma->second.size == block->size()); | ||||
|         CHECK(vma->second.size == block.GetSize()); | ||||
|         CHECK(vma->second.type == Kernel::VMAType::BackingMemory); | ||||
|         CHECK(vma->second.backing_memory == block->data()); | ||||
|         CHECK(vma->second.backing_memory.GetPtr() == block.GetPtr()); | ||||
|         CHECK(vma->second.meminfo_state == Kernel::MemoryState::Private); | ||||
|     } | ||||
| 
 | ||||
|     SECTION("unmapping memory") { | ||||
|         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
 | ||||
|         auto manager = std::make_unique<Kernel::VMManager>(memory); | ||||
|         auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block->data(), block->size(), | ||||
|         auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, block.GetSize(), | ||||
|                                                 Kernel::MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         ResultCode code = manager->UnmapRange(Memory::HEAP_VADDR, block->size()); | ||||
|         ResultCode code = manager->UnmapRange(Memory::HEAP_VADDR, block.GetSize()); | ||||
|         REQUIRE(code == RESULT_SUCCESS); | ||||
| 
 | ||||
|         auto vma = manager->FindVMA(Memory::HEAP_VADDR); | ||||
|         CHECK(vma != manager->vma_map.end()); | ||||
|         CHECK(vma->second.type == Kernel::VMAType::Free); | ||||
|         CHECK(vma->second.backing_memory == nullptr); | ||||
|         CHECK(vma->second.backing_memory.GetPtr() == nullptr); | ||||
|     } | ||||
| 
 | ||||
|     SECTION("changing memory permissions") { | ||||
|         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
 | ||||
|         auto manager = std::make_unique<Kernel::VMManager>(memory); | ||||
|         auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block->data(), block->size(), | ||||
|         auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, block.GetSize(), | ||||
|                                                 Kernel::MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block->size(), | ||||
|         ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block.GetSize(), | ||||
|                                                   Kernel::VMAPermission::Execute); | ||||
|         CHECK(code == RESULT_SUCCESS); | ||||
| 
 | ||||
|  | @ -58,24 +59,24 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||
|         CHECK(vma != manager->vma_map.end()); | ||||
|         CHECK(vma->second.permissions == Kernel::VMAPermission::Execute); | ||||
| 
 | ||||
|         code = manager->UnmapRange(Memory::HEAP_VADDR, block->size()); | ||||
|         code = manager->UnmapRange(Memory::HEAP_VADDR, block.GetSize()); | ||||
|         REQUIRE(code == RESULT_SUCCESS); | ||||
|     } | ||||
| 
 | ||||
|     SECTION("changing memory state") { | ||||
|         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
 | ||||
|         auto manager = std::make_unique<Kernel::VMManager>(memory); | ||||
|         auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block->data(), block->size(), | ||||
|         auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, block.GetSize(), | ||||
|                                                 Kernel::MemoryState::Private); | ||||
|         REQUIRE(result.Code() == RESULT_SUCCESS); | ||||
| 
 | ||||
|         ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block->size(), | ||||
|         ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block.GetSize(), | ||||
|                                                   Kernel::VMAPermission::ReadWrite); | ||||
|         REQUIRE(code == RESULT_SUCCESS); | ||||
| 
 | ||||
|         SECTION("with invalid address") { | ||||
|             ResultCode code = manager->ChangeMemoryState( | ||||
|                 0xFFFFFFFF, block->size(), Kernel::MemoryState::Locked, | ||||
|                 0xFFFFFFFF, block.GetSize(), Kernel::MemoryState::Locked, | ||||
|                 Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased, | ||||
|                 Kernel::VMAPermission::Execute); | ||||
|             CHECK(code == Kernel::ERR_INVALID_ADDRESS); | ||||
|  | @ -83,7 +84,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||
| 
 | ||||
|         SECTION("ignoring the original permissions") { | ||||
|             ResultCode code = manager->ChangeMemoryState( | ||||
|                 Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private, | ||||
|                 Memory::HEAP_VADDR, block.GetSize(), Kernel::MemoryState::Private, | ||||
|                 Kernel::VMAPermission::None, Kernel::MemoryState::Locked, | ||||
|                 Kernel::VMAPermission::Write); | ||||
|             CHECK(code == RESULT_SUCCESS); | ||||
|  | @ -96,7 +97,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||
| 
 | ||||
|         SECTION("enforcing the original permissions with correct expectations") { | ||||
|             ResultCode code = manager->ChangeMemoryState( | ||||
|                 Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private, | ||||
|                 Memory::HEAP_VADDR, block.GetSize(), Kernel::MemoryState::Private, | ||||
|                 Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased, | ||||
|                 Kernel::VMAPermission::Execute); | ||||
|             CHECK(code == RESULT_SUCCESS); | ||||
|  | @ -109,7 +110,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||
| 
 | ||||
|         SECTION("with incorrect permission expectations") { | ||||
|             ResultCode code = manager->ChangeMemoryState( | ||||
|                 Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private, | ||||
|                 Memory::HEAP_VADDR, block.GetSize(), Kernel::MemoryState::Private, | ||||
|                 Kernel::VMAPermission::Execute, Kernel::MemoryState::Aliased, | ||||
|                 Kernel::VMAPermission::Execute); | ||||
|             CHECK(code == Kernel::ERR_INVALID_ADDRESS_STATE); | ||||
|  | @ -122,7 +123,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||
| 
 | ||||
|         SECTION("with incorrect state expectations") { | ||||
|             ResultCode code = manager->ChangeMemoryState( | ||||
|                 Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Locked, | ||||
|                 Memory::HEAP_VADDR, block.GetSize(), Kernel::MemoryState::Locked, | ||||
|                 Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased, | ||||
|                 Kernel::VMAPermission::Execute); | ||||
|             CHECK(code == Kernel::ERR_INVALID_ADDRESS_STATE); | ||||
|  | @ -133,7 +134,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") { | |||
|             CHECK(vma->second.meminfo_state == Kernel::MemoryState::Private); | ||||
|         } | ||||
| 
 | ||||
|         code = manager->UnmapRange(Memory::HEAP_VADDR, block->size()); | ||||
|         code = manager->UnmapRange(Memory::HEAP_VADDR, block.GetSize()); | ||||
|         REQUIRE(code == RESULT_SUCCESS); | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue