Compare commits
4 Commits
471beb8130
...
2a19fd42de
Author | SHA1 | Date | |
---|---|---|---|
2a19fd42de | |||
43c6c75710 | |||
65e33fdad5 | |||
3aae765c9c |
@ -20,6 +20,7 @@ Abstract:
|
|||||||
|
|
||||||
#define MDL_OPERATION_FLAGS_TRUNCATE 0x00000002
|
#define MDL_OPERATION_FLAGS_TRUNCATE 0x00000002
|
||||||
#define MDL_OPERATION_FLAGS_PHYSICAL 0x40000000
|
#define MDL_OPERATION_FLAGS_PHYSICAL 0x40000000
|
||||||
|
#define MDL_OPERATION_FLAGS_VIRTUAL 0x80000000
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
MmFwGetMemoryMap (
|
MmFwGetMemoryMap (
|
||||||
|
@ -421,6 +421,7 @@ Return Value:
|
|||||||
|
|
||||||
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
||||||
if (!NT_SUCCESS(Status)) {
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NtDescriptor);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,6 +457,7 @@ Return Value:
|
|||||||
|
|
||||||
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
||||||
if (!NT_SUCCESS(Status)) {
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NtDescriptor);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,6 +488,7 @@ Return Value:
|
|||||||
|
|
||||||
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
||||||
if (!NT_SUCCESS(Status)) {
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NtDescriptor);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -507,6 +510,7 @@ Return Value:
|
|||||||
|
|
||||||
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
||||||
if (!NT_SUCCESS(Status)) {
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NtDescriptor);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -568,6 +572,7 @@ Return Value:
|
|||||||
|
|
||||||
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
Status = MmMdAddDescriptorToList(Mdl, NtDescriptor, MDL_OPERATION_FLAGS_TRUNCATE);
|
||||||
if (!NT_SUCCESS(Status)) {
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NtDescriptor);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -611,7 +616,7 @@ Return Value:
|
|||||||
//
|
//
|
||||||
// Remove the current descriptor.
|
// Remove the current descriptor.
|
||||||
//
|
//
|
||||||
Status = MmMdRemoveRegionFromMdlEx(Mdl, NtStartPage, NtPageCount, MDL_OPERATION_FLAGS_PHYSICAL, 0);
|
Status = MmMdRemoveRegionFromMdlEx(Mdl, NtStartPage, NtPageCount, MDL_OPERATION_FLAGS_PHYSICAL, NULL);
|
||||||
if (!NT_SUCCESS(Status)) {
|
if (!NT_SUCCESS(Status)) {
|
||||||
MmMdFreeDescriptor(NtDescriptor);
|
MmMdFreeDescriptor(NtDescriptor);
|
||||||
goto exit;
|
goto exit;
|
||||||
|
@ -152,6 +152,7 @@ Return Value:
|
|||||||
--*/
|
--*/
|
||||||
|
|
||||||
{
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
PMEMORY_DESCRIPTOR PrevDescriptor, NextDescriptor, NewDescriptor;
|
PMEMORY_DESCRIPTOR PrevDescriptor, NextDescriptor, NewDescriptor;
|
||||||
ULONGLONG DescriptorEnd, PrevDescriptorEnd, NextDescriptorEnd;
|
ULONGLONG DescriptorEnd, PrevDescriptorEnd, NextDescriptorEnd;
|
||||||
ULONGLONG MappedFirstPage;
|
ULONGLONG MappedFirstPage;
|
||||||
@ -186,7 +187,10 @@ Return Value:
|
|||||||
PrevDescriptor->Type
|
PrevDescriptor->Type
|
||||||
);
|
);
|
||||||
if (NewDescriptor != NULL) {
|
if (NewDescriptor != NULL) {
|
||||||
MmMdAddDescriptorToList(Mdl, NewDescriptor, Flags);
|
Status = MmMdAddDescriptorToList(Mdl, NewDescriptor, Flags);
|
||||||
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NewDescriptor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +246,10 @@ Return Value:
|
|||||||
Descriptor->Type
|
Descriptor->Type
|
||||||
);
|
);
|
||||||
if (NewDescriptor != NULL) {
|
if (NewDescriptor != NULL) {
|
||||||
MmMdAddDescriptorToList(Mdl, NewDescriptor, Flags);
|
Status = MmMdAddDescriptorToList(Mdl, NewDescriptor, Flags);
|
||||||
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NewDescriptor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,6 +317,7 @@ Return Value:
|
|||||||
PMEMORY_DESCRIPTOR CurrentDescriptor;
|
PMEMORY_DESCRIPTOR CurrentDescriptor;
|
||||||
|
|
||||||
if (Mdl == NULL || Descriptor == NULL) {
|
if (Mdl == NULL || Descriptor == NULL) {
|
||||||
|
DebugPrint(L"MmMdAddDescriptorToList(): Mdl and/or Descriptor are NULL\r\n");
|
||||||
return STATUS_INVALID_PARAMETER;
|
return STATUS_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,9 +462,61 @@ Return Value:
|
|||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
BOOLEAN Mapped;
|
||||||
|
PMEMORY_DESCRIPTOR Descriptor;
|
||||||
|
PLIST_ENTRY ListEntry;
|
||||||
|
ULONGLONG FirstPage;
|
||||||
|
|
||||||
|
Mapped = FALSE;
|
||||||
|
if (Flags & MDL_OPERATION_FLAGS_VIRTUAL) {
|
||||||
|
if (Mdl->Type == MDL_TYPE_PHYSICAL) {
|
||||||
|
Mapped = TRUE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
// If the MDL is virtual, the
|
||||||
|
// virtual flag must be set.
|
||||||
|
//
|
||||||
|
if (Mdl->Type == MDL_TYPE_VIRTUAL) {
|
||||||
|
DebugPrint(L"MmMdFindDescriptorFromMdl(): Flags is invalid\r\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// TODO: Implement this routine.
|
// Check if the cached descriptor is in range.
|
||||||
//
|
//
|
||||||
|
if (!Mapped && Mdl->Current != NULL) {
|
||||||
|
Descriptor = (PMEMORY_DESCRIPTOR)Mdl->Current;
|
||||||
|
if (Page < Descriptor->FirstPage) {
|
||||||
|
ListEntry = Mdl->Head->Flink;
|
||||||
|
} else {
|
||||||
|
ListEntry = Mdl->Current;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ListEntry = Mdl->Head->Flink;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ListEntry != Mdl->Head) {
|
||||||
|
Descriptor = (PMEMORY_DESCRIPTOR)ListEntry;
|
||||||
|
|
||||||
|
if (Mapped) {
|
||||||
|
FirstPage = Descriptor->MappedFirstPage;
|
||||||
|
} else {
|
||||||
|
FirstPage = Descriptor->FirstPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Check if this descriptor contains Page.
|
||||||
|
//
|
||||||
|
if ((!Mapped || FirstPage)
|
||||||
|
&& Page >= FirstPage
|
||||||
|
&& Page < FirstPage + Descriptor->PageCount) {
|
||||||
|
return Descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListEntry = ListEntry->Flink;
|
||||||
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -464,7 +524,7 @@ Return Value:
|
|||||||
NTSTATUS
|
NTSTATUS
|
||||||
MmMdRemoveRegionFromMdlEx (
|
MmMdRemoveRegionFromMdlEx (
|
||||||
IN PMEMORY_DESCRIPTOR_LIST Mdl,
|
IN PMEMORY_DESCRIPTOR_LIST Mdl,
|
||||||
IN ULONGLONG FirstPage,
|
IN ULONGLONG RemoveStart,
|
||||||
IN ULONGLONG PageCount,
|
IN ULONGLONG PageCount,
|
||||||
IN ULONG Flags,
|
IN ULONG Flags,
|
||||||
OUT PMEMORY_DESCRIPTOR_LIST Unused
|
OUT PMEMORY_DESCRIPTOR_LIST Unused
|
||||||
@ -480,46 +540,154 @@ Arguments:
|
|||||||
|
|
||||||
Mdl - MDL to remove the region from.
|
Mdl - MDL to remove the region from.
|
||||||
|
|
||||||
FirstPage - The first page in the region.
|
RemoveStart - The first page in the region.
|
||||||
|
|
||||||
PageCount - The number of pages in the region.
|
PageCount - The number of pages in the region.
|
||||||
|
|
||||||
Flags - MDL_OPERATION_FLAGS_*.
|
Flags - MDL_OPERATION_FLAGS_*.
|
||||||
|
MDL_OPERATION_FLAGS_PHYSICAL if the region is physical.
|
||||||
|
MDL_OPERATION_FLAGS_VIRTUAL if the region is virtual.
|
||||||
|
|
||||||
Unused - Unused.
|
Unused - Unused.
|
||||||
|
|
||||||
Return Value:
|
Return Value:
|
||||||
|
|
||||||
None.
|
STATUS_SUCCESS if successful,
|
||||||
|
STATUS_INVALID_PARAMETER if Flags value is invalid.
|
||||||
|
|
||||||
--*/
|
--*/
|
||||||
|
|
||||||
{
|
{
|
||||||
ULONGLONG RemoveEnd, DescriptorEnd;
|
NTSTATUS Status;
|
||||||
PLIST_ENTRY Entry;
|
ULONG Offset;
|
||||||
PMEMORY_DESCRIPTOR Descriptor;
|
BOOLEAN Mapped;
|
||||||
MEMORY_DESCRIPTOR RemovedDescriptor;
|
ULONGLONG RemoveEnd;
|
||||||
|
PLIST_ENTRY ListEntry;
|
||||||
|
ULONGLONG DescriptorStart, DescriptorEnd;
|
||||||
|
PMEMORY_DESCRIPTOR Descriptor, NewDescriptor;
|
||||||
|
|
||||||
(VOID)Flags;
|
|
||||||
(VOID)Unused;
|
(VOID)Unused;
|
||||||
|
|
||||||
RemoveEnd = FirstPage + PageCount;
|
Mapped = FALSE;
|
||||||
Entry = Mdl->Head->Flink;
|
if (Flags & MDL_OPERATION_FLAGS_VIRTUAL) {
|
||||||
while (Entry != Mdl->Head) {
|
if (Mdl->Type == MDL_TYPE_PHYSICAL) {
|
||||||
Descriptor = (PMEMORY_DESCRIPTOR)Entry;
|
Mapped = TRUE;
|
||||||
DescriptorEnd = Descriptor->FirstPage + Descriptor->PageCount;
|
}
|
||||||
|
} else {
|
||||||
RtlCopyMemory(&RemovedDescriptor, Descriptor, sizeof(MEMORY_DESCRIPTOR));
|
//
|
||||||
|
// If the MDL is virtual, the
|
||||||
// if (FirstPage <= Descriptor->FirstPage && Descriptor->FirstPage < RemoveEnd) {
|
// virtual flag must be set.
|
||||||
// }
|
//
|
||||||
|
if (Mdl->Type == MDL_TYPE_VIRTUAL) {
|
||||||
|
DebugPrint(L"MmMdRemoveRegionFromMdlEx(): Flags is invalid\r\n");
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
RemoveEnd = RemoveStart + PageCount;
|
||||||
// TODO: Implement the rest of this routine.
|
ListEntry = Mdl->Head->Flink;
|
||||||
//
|
while (ListEntry != Mdl->Head) {
|
||||||
|
Descriptor = (PMEMORY_DESCRIPTOR)ListEntry;
|
||||||
|
|
||||||
return STATUS_SUCCESS;
|
if (Mapped) {
|
||||||
|
DescriptorStart = Descriptor->MappedFirstPage;
|
||||||
|
} else {
|
||||||
|
DescriptorStart = Descriptor->FirstPage;
|
||||||
|
}
|
||||||
|
DescriptorEnd = DescriptorStart + Descriptor->PageCount;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Check if the region to be removed
|
||||||
|
// is inside the current descriptor.
|
||||||
|
//
|
||||||
|
if (RemoveStart <= DescriptorStart && RemoveEnd > DescriptorStart) {
|
||||||
|
//
|
||||||
|
// The region is around the start of the descriptor, or
|
||||||
|
// they have identical locations and sizes.
|
||||||
|
// | RemoveStart | DescriptorStart | RemoveEnd | DescriptorEnd |
|
||||||
|
// | Lower address ............................ Higher address |
|
||||||
|
//
|
||||||
|
|
||||||
|
if (RemoveEnd < DescriptorEnd) {
|
||||||
|
Offset = RemoveEnd - DescriptorStart;
|
||||||
|
} else {
|
||||||
|
Offset = DescriptorEnd - DescriptorStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Shrink the descriptor.
|
||||||
|
//
|
||||||
|
Descriptor->FirstPage += Offset;
|
||||||
|
Descriptor->PageCount -= Offset;
|
||||||
|
if (Descriptor->MappedFirstPage) {
|
||||||
|
Descriptor->MappedFirstPage += Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Remove descriptor if now empty.
|
||||||
|
//
|
||||||
|
if (Descriptor->PageCount == 0) {
|
||||||
|
MmMdRemoveDescriptorFromList(Mdl, Descriptor);
|
||||||
|
MmMdFreeDescriptor(Descriptor);
|
||||||
|
}
|
||||||
|
} else if (RemoveStart < DescriptorEnd && RemoveEnd >= DescriptorEnd) {
|
||||||
|
//
|
||||||
|
// The region is around the end of the descriptor.
|
||||||
|
// | DescriptorStart | RemoveStart | DescriptorEnd | RemoveEnd |
|
||||||
|
// | Lower address ............................ Higher address |
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Simply shrink the descriptor.
|
||||||
|
//
|
||||||
|
Descriptor->PageCount -= DescriptorEnd - RemoveStart;
|
||||||
|
} else if (RemoveStart > DescriptorStart && RemoveEnd < DescriptorEnd) {
|
||||||
|
//
|
||||||
|
// The region is completely inside the descriptor.
|
||||||
|
// In this case, the descriptor must be split in two.
|
||||||
|
// | DescriptorStart | RemoveStart | RemoveEnd | DescriptorEnd |
|
||||||
|
// | Lower address ............................ Higher address |
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Create a new descriptor before the removed region.
|
||||||
|
//
|
||||||
|
NewDescriptor = MmMdInitDescriptor(
|
||||||
|
Descriptor->FirstPage,
|
||||||
|
Descriptor->MappedFirstPage,
|
||||||
|
RemoveStart - DescriptorStart,
|
||||||
|
Descriptor->Attributes,
|
||||||
|
Descriptor->Type
|
||||||
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Shrink and move the current descriptor.
|
||||||
|
//
|
||||||
|
Offset = NewDescriptor->PageCount + PageCount;
|
||||||
|
Descriptor->FirstPage += Offset;
|
||||||
|
Descriptor->PageCount -= Offset;
|
||||||
|
if (Descriptor->MappedFirstPage) {
|
||||||
|
Descriptor->MappedFirstPage += Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Now check if MmMdInitDescriptor() actually worked.
|
||||||
|
//
|
||||||
|
if (NewDescriptor == NULL) {
|
||||||
|
return STATUS_NO_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status = MmMdAddDescriptorToList(Mdl, NewDescriptor, Flags);
|
||||||
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NewDescriptor);
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListEntry = ListEntry->Flink;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
NTSTATUS
|
NTSTATUS
|
||||||
@ -567,7 +735,7 @@ Return Value:
|
|||||||
// Free the descriptor from the heap.
|
// Free the descriptor from the heap.
|
||||||
// TODO: Use BlMmFreeHeap().
|
// TODO: Use BlMmFreeHeap().
|
||||||
//
|
//
|
||||||
ConsolePrint(L"MmMdFreeDescriptor(): need BlMmFreeHeap() to free descriptor\r\n");
|
ConsolePrint(L"MmMdFreeDescriptor(): Heap not available\r\n");
|
||||||
return STATUS_NOT_IMPLEMENTED;
|
return STATUS_NOT_IMPLEMENTED;
|
||||||
// return BlMmFreeHeap(Descriptor);
|
// return BlMmFreeHeap(Descriptor);
|
||||||
}
|
}
|
||||||
|
@ -139,8 +139,9 @@ Return Value:
|
|||||||
return STATUS_NO_MEMORY;
|
return STATUS_NO_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status = MmMdAddDescriptorToList(&MmMdlReservedAllocated, NewDescriptor, 0x00);
|
Status = MmMdAddDescriptorToList(&MmMdlReservedAllocated, NewDescriptor, 0);
|
||||||
if (!NT_SUCCESS(Status)) {
|
if (!NT_SUCCESS(Status)) {
|
||||||
|
MmMdFreeDescriptor(NewDescriptor);
|
||||||
return Status;
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,63 +105,6 @@ Return Value:
|
|||||||
return Status;
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Print debug information.
|
|
||||||
// TODO: Remove this once the project is more stable?
|
|
||||||
//
|
|
||||||
#ifdef _DEBUG
|
|
||||||
DebugPrint(L"Boot device type: ");
|
|
||||||
switch (BlpBootDevice->Type) {
|
|
||||||
case BOOT_DEVICE_TYPE_PARTITION:
|
|
||||||
DebugPrint(L"partition\r\n");
|
|
||||||
BlockDevice = &BlpBootDevice->Partition.Parent;
|
|
||||||
break;
|
|
||||||
case BOOT_DEVICE_TYPE_PARTITION_EX:
|
|
||||||
DebugPrint(L"partition\r\n");
|
|
||||||
BlockDevice = &BlpBootDevice->PartitionEx.Parent;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
DebugPrint(L"generic block device\r\n");
|
|
||||||
BlockDevice = &BlpBootDevice->Block;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugPrint(L"Boot device parent type: ");
|
|
||||||
switch (BlockDevice->Type) {
|
|
||||||
case BOOT_BLOCK_DEVICE_TYPE_HARDDRIVE:
|
|
||||||
DebugPrint(L"hard drive\r\n");
|
|
||||||
break;
|
|
||||||
case BOOT_BLOCK_DEVICE_TYPE_CDROM:
|
|
||||||
DebugPrint(L"CD-ROM\r\n");
|
|
||||||
break;
|
|
||||||
case BOOT_BLOCK_DEVICE_TYPE_RAMDISK:
|
|
||||||
DebugPrint(L"RAM disk\r\n");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
DebugPrint(L"generic block device\r\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Option = &ApplicationEntry->Options;
|
|
||||||
for (ULONG Index = 0; !Option->IsInvalid; Index++) {
|
|
||||||
DebugPrintf(L"Boot entry option %x: ", Index);
|
|
||||||
|
|
||||||
if (Option->Type == BCDE_DATA_TYPE_APPLICATION_PATH) {
|
|
||||||
DebugPrint(L"application path \"");
|
|
||||||
DebugPrint((PWSTR)((PUCHAR)Option + Option->DataOffset));
|
|
||||||
DebugPrint(L"\"\r\n");
|
|
||||||
} else {
|
|
||||||
DebugPrintf(L"type %x, data size %x\r\n", Option->Type, Option->DataSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Option->NextOptionOffset == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Option = (PBOOT_APPLICATION_OPTION)((PUCHAR)Option + Option->NextOptionOffset);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user