Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Brovan/Brovan.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down Expand Up @@ -33,8 +33,13 @@
<Copy SourceFiles="$(ProjectDir)Resources\libunicorn.so" DestinationFolder="$(PublishDir)" Condition="'$(RuntimeIdentifier)' != '' And $([System.String]::Copy('$(RuntimeIdentifier)').Contains('linux'))" SkipUnchangedFiles="true" />
</Target>

<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<Exec Command="@echo off&#xA;:: Detect if this is a Linux target build&#xA;set &quot;RID=$(RuntimeIdentifier)&quot;&#xA;set &quot;IS_LINUX=0&quot;&#xA;if not &quot;%25RID%25&quot;==&quot;&quot; (&#xA; echo %25RID%25 | findstr /i &quot;linux&quot; &gt;nul 2&gt;&amp;1&#xA; if not errorlevel 1 set &quot;IS_LINUX=1&quot;&#xA;)&#xA;&#xA;:: Copy the appropriate unicorn library to the target directory&#xA;if &quot;%25IS_LINUX%25&quot;==&quot;1&quot; (&#xA; copy /Y &quot;$(ProjectDir)Resources\libunicorn.so&quot; &quot;$(TargetDir)&quot; &gt;nul 2&gt;&amp;1&#xA; if errorlevel 1 (&#xA; echo Warning: Failed to copy libunicorn.so to the target path.&#xA; ) else (&#xA; echo Copied libunicorn.so to the target path successfully.&#xA; )&#xA;) else (&#xA; copy /Y &quot;$(ProjectDir)Resources\unicorn.dll&quot; &quot;$(TargetDir)&quot; &gt;nul 2&gt;&amp;1&#xA; if errorlevel 1 (&#xA; echo Warning: Failed to copy unicorn.dll to the target path.&#xA; ) else (&#xA; echo Copied unicorn.dll to the target path successfully.&#xA; )&#xA;)&#xA;&#xA;:: Skip editbin entirely for Linux builds&#xA;if &quot;%25IS_LINUX%25&quot;==&quot;1&quot; (&#xA; echo Skipping editbin for Linux build.&#xA; exit /b 0&#xA;)&#xA;&#xA;:: Get the target path (which is the dll)&#xA;set &quot;ORIGINAL_PATH=$(TargetPath)&quot;&#xA;:: Replace the target path dll extension with an exe extension to pass it to editbin later.&#xA;set &quot;MODIFIED_PATH=%25ORIGINAL_PATH:.dll=.exe%25&quot;&#xA;echo Calling VsDevCmd.bat...&#xA;call &quot;$(DevEnvDir)..\Tools\VsDevCmd.bat&quot; &gt;nul 2&gt;&amp;1&#xA;if errorlevel 1 (&#xA; echo Warning: Couldn't execute visual studio developer command prompt.&#xA; exit /b 0&#xA;)&#xA;:: Check if editbin.exe is now available&#xA;where editbin.exe &gt;nul 2&gt;&amp;1&#xA;if errorlevel 1 (&#xA; echo Warning: editbin.exe not found in environment. CFG won't be disabled, Brovan will try to disable it at runtime.&#xA;) else (&#xA; :: Disable CFG using editbin.exe&#xA; editbin.exe /GUARD:NO %25MODIFIED_PATH%25 &gt;nul 2&gt;&amp;1&#xA; if errorlevel 1 (&#xA; echo editbin.exe failed to run, CFG won't be disabled so Brovan will try to disable it at runtime.&#xA; ) else (&#xA; echo Disabled CFG using editbin.exe successfully.&#xA; )&#xA;)&#xA;exit /b 0" />
<PropertyGroup>
<_ModifiedTargetPath>$([System.String]::Copy('$(TargetPath)').Replace('.dll', '.exe'))</_ModifiedTargetPath>
</PropertyGroup>

<Target Name="PostBuild" AfterTargets="Build" Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<Message Text="Skipping editbin for Linux build." Importance="high" Condition="'$(RuntimeIdentifier)' != '' And $([System.String]::Copy('$(RuntimeIdentifier)').Contains('linux'))" />
<Exec Command="call &quot;$(DevEnvDir)..\Tools\VsDevCmd.bat&quot; &gt;nul 2&gt;&amp;1 &amp;&amp; where editbin.exe &gt;nul 2&gt;&amp;1 &amp;&amp; editbin.exe /GUARD:NO &quot;$(_ModifiedTargetPath)&quot; &gt;nul 2&gt;&amp;1" Condition="'$(RuntimeIdentifier)' == '' Or $([System.String]::Copy('$(RuntimeIdentifier)').Contains('linux')) == false" IgnoreExitCode="true" />
</Target>

<ItemGroup>
Expand Down
79 changes: 79 additions & 0 deletions Brovan/Core/Emulation/OS/Windows/Devices/KsecDevice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Security.Cryptography;

namespace Brovan.Core.Emulation.OS.Windows
{
/// <summary>
/// Emulates <c>\Device\KsecDD</c> (Kernel Security Support Provider device)
/// </summary>
internal sealed class KsecDevice : IWinDevice
{
public string DeviceName => "\\Device\\KsecDD";

private const uint IOCTL_KSEC_RNG = 0x390004;
private const uint IOCTL_KSEC_RNG_REKEY = 0x390008;
private const uint IOCTL_KSEC_ENCRYPT_MEMORY = 0x39000C;
private const uint IOCTL_KSEC_DECRYPT_MEMORY = 0x390010;
private const uint IOCTL_KSEC_ENCRYPT_MEMORY_CROSS_PROCESS = 0x390014;
private const uint IOCTL_KSEC_DECRYPT_MEMORY_CROSS_PROCESS = 0x390018;
private const uint IOCTL_KSEC_ENCRYPT_MEMORY_SAME_LOGON = 0x39001C;
private const uint IOCTL_KSEC_DECRYPT_MEMORY_SAME_LOGON = 0x390020;
private const uint IOCTL_KSEC_CLIENT_HANDSHAKE = 0x390400;

public NTSTATUS Create(BinaryEmulator Instance, string DevicePath, byte[] EaBuffer, out string InternalPath, out WinDeviceDelegate Handler)
{
InternalPath = DevicePath;
Handler = Handle;
return NTSTATUS.STATUS_SUCCESS;
}

private NTSTATUS Handle(uint IOCTL, ref DeviceData Data, BinaryEmulator Instance)
{
switch (IOCTL)
{
case IOCTL_KSEC_RNG:
case IOCTL_KSEC_RNG_REKEY:
if (Data.OutputBuffer == null || Data.OutputLength == 0)
return NTSTATUS.STATUS_INVALID_PARAMETER;

uint Size = Math.Min(Data.OutputLength, (uint)Data.OutputBuffer.Length);
if (Size == 0)
return NTSTATUS.STATUS_INVALID_PARAMETER;

RandomNumberGenerator.Fill(Data.OutputBuffer.AsSpan(0, (int)Size));
Data.Information = Size;
return NTSTATUS.STATUS_SUCCESS;

case IOCTL_KSEC_ENCRYPT_MEMORY:
case IOCTL_KSEC_DECRYPT_MEMORY:
case IOCTL_KSEC_ENCRYPT_MEMORY_CROSS_PROCESS:
case IOCTL_KSEC_DECRYPT_MEMORY_CROSS_PROCESS:
case IOCTL_KSEC_ENCRYPT_MEMORY_SAME_LOGON:
case IOCTL_KSEC_DECRYPT_MEMORY_SAME_LOGON:
{
byte[] Source = (Data.InputBuffer != null && Data.InputBuffer.Length > 0) ? Data.InputBuffer : Data.OutputBuffer;
if (Data.OutputBuffer != null && Source != null)
{
int Count = Math.Min(Data.OutputBuffer.Length, Source.Length);
if (!ReferenceEquals(Source, Data.OutputBuffer))
Array.Copy(Source, Data.OutputBuffer, Count);
Data.Information = (uint)Count;
}
return NTSTATUS.STATUS_SUCCESS;
}

case IOCTL_KSEC_CLIENT_HANDSHAKE:
if (Data.OutputBuffer != null && Data.OutputLength > 0)
{
uint OutN = Math.Min(Data.OutputLength, (uint)Data.OutputBuffer.Length);
Data.OutputBuffer.AsSpan(0, (int)OutN).Clear();
Data.Information = OutN;
}
return NTSTATUS.STATUS_SUCCESS;

default:
return NTSTATUS.STATUS_INVALID_DEVICE_REQUEST;
}
}
}
}
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Files/NtCreateFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
private NTSTATUS Handle64(BinaryEmulator Instance)
{
ulong FileHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong ObjectAttributesPtr = Instance.WinHelper.GetArg64(2);
ulong IoStatusBlockPtr = Instance.WinHelper.GetArg64(3);
uint CreateDisposition = (uint)Instance.WinHelper.GetArg64(7);
Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Files/NtCreateSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
return Instance.WinUnimplemented;

ulong SectionHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong ObjectAttributesPtr = Instance.WinHelper.GetArg64(2);
ulong MaximumSizePtr = Instance.WinHelper.GetArg64(3);
uint SectionPageProtection = (uint)Instance.WinHelper.GetArg64(4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public NTSTATUS Handle(BinaryEmulator Instance)
ulong FileInformation = Instance.WinHelper.GetArg64(5);
uint Length = (uint)Instance.WinHelper.GetArg64(6);
uint FileInformationClass = (uint)Instance.WinHelper.GetArg64(7);
bool ReturnSingleEntry = Instance.WinHelper.GetArg64(8) != 0;
bool ReturnSingleEntry = (uint)Instance.WinHelper.GetArg64(8) != 0;
ulong FileName = Instance.WinHelper.GetArg64(9);
bool RestartScan = Instance.WinHelper.GetArg64(10) != 0;
bool RestartScan = (uint)Instance.WinHelper.GetArg64(10) != 0;

uint QueryFlags = 0;
if (RestartScan)
Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Misc/NtContinue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class NtContinue : IWinSyscall
public NTSTATUS Handle(BinaryEmulator Instance)
{
ulong ContextPtr = Instance.WinHelper.GetArg64(0);
bool TestAlert = Instance.WinHelper.GetArg64(1) != 0;
bool TestAlert = (uint)Instance.WinHelper.GetArg64(1) != 0;

return Continue(Instance, ContextPtr, TestAlert);
}
Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Misc/NtCreateEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
if (Instance._binary.Architecture == BinaryArchitecture.x64)
{
ulong EventHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong ObjectAttributes = Instance.WinHelper.GetArg64(2);
uint EventType = (uint)Instance.WinHelper.GetArg64(3);
bool InitialState = (byte)Instance.WinHelper.GetArg64(4, true) != 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
if (Instance._binary.Architecture == BinaryArchitecture.x64)
{
ulong IoCompletionHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong Count = Instance.WinHelper.GetArg64(3);

if (IoCompletionHandlePtr == 0)
Expand Down
4 changes: 2 additions & 2 deletions Brovan/Core/Emulation/OS/Windows/Misc/NtCreateMutant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ public NTSTATUS Handle(BinaryEmulator Instance)
if (Instance._binary.Architecture == BinaryArchitecture.x64)
{
ulong MutantHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong ObjectAttributesPtr = Instance.WinHelper.GetArg64(2);
bool InitialOwner = Instance.WinHelper.GetArg64(3) != 0;
bool InitialOwner = (uint)Instance.WinHelper.GetArg64(3) != 0;

return HandleCreateMutant64(Instance, MutantHandlePtr, DesiredAccess, ObjectAttributesPtr, InitialOwner);
}
Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Misc/NtCreateSemaphore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
if (Instance._binary.Architecture == BinaryArchitecture.x64)
{
ulong SemaphoreHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong ObjectAttributesPtr = Instance.WinHelper.GetArg64(2);
int InitialCount = (int)Instance.WinHelper.GetArg64(3, true);
int MaximumCount = (int)Instance.WinHelper.GetArg64(4, true);
Expand Down
4 changes: 2 additions & 2 deletions Brovan/Core/Emulation/OS/Windows/Misc/NtCreateTimer2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public NTSTATUS Handle(BinaryEmulator Instance)
ulong TimerHandlePtr = Instance.WinHelper.GetArg64(0);
ulong TimerIdPtr = Instance.WinHelper.GetArg64(1);
ulong ObjectAttributesPtr = Instance.WinHelper.GetArg64(2);
ulong Attributes = Instance.WinHelper.GetArg64(3);
ulong DesiredAccess = Instance.WinHelper.GetArg64(4);
ulong Attributes = (uint)Instance.WinHelper.GetArg64(3);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(4);

if (TimerHandlePtr == 0)
return NTSTATUS.STATUS_INVALID_PARAMETER;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
if (Instance._binary.Architecture == BinaryArchitecture.x64)
{
ulong WaitCompletionPacketHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong ObjectAttributes = Instance.WinHelper.GetArg64(2);

if (WaitCompletionPacketHandlePtr == 0)
Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Misc/NtDelayExecution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
if (Instance._binary.Architecture != BinaryArchitecture.x64)
return Instance.WinUnimplemented;

bool Alertable = Instance.WinHelper.GetArg64(0) != 0;
bool Alertable = (uint)Instance.WinHelper.GetArg64(0) != 0;
ulong DelayIntervalPtr = Instance.WinHelper.GetArg64(1);
long DelayMs = ReadDelayMs(Instance, DelayIntervalPtr);
EmulatedThread Thread = Instance.CurrentThread;
Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Misc/NtDuplicateObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
ulong SourceHandle = Instance.WinHelper.GetArg64(1);
ulong TargetProcessHandle = Instance.WinHelper.GetArg64(2);
ulong TargetHandlePtr = Instance.WinHelper.GetArg64(3);
ulong DesiredAccess = Instance.WinHelper.GetArg64(4);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(4);
uint HandleAttributes = (uint)Instance.WinHelper.GetArg64(5);
uint Options = (uint)Instance.WinHelper.GetArg64(6);

Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Misc/NtOpenMutant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
if (Instance._binary.Architecture == BinaryArchitecture.x64)
{
ulong MutantHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong ObjectAttributesPtr = Instance.WinHelper.GetArg64(2);

return HandleOpenMutant64(Instance, MutantHandlePtr, DesiredAccess, ObjectAttributesPtr);
Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Misc/NtOpenSemaphore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
if (Instance._binary.Architecture == BinaryArchitecture.x64)
{
ulong SemaphoreHandlePtr = Instance.WinHelper.GetArg64(0);
ulong DesiredAccess = Instance.WinHelper.GetArg64(1);
ulong DesiredAccess = (uint)Instance.WinHelper.GetArg64(1);
ulong ObjectAttributesPtr = Instance.WinHelper.GetArg64(2);

return HandleOpenSemaphore64(Instance, SemaphoreHandlePtr, DesiredAccess, ObjectAttributesPtr);
Expand Down
2 changes: 1 addition & 1 deletion Brovan/Core/Emulation/OS/Windows/Misc/NtRaiseException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
ulong ExceptionRecordPtr = Instance.WinHelper.GetArg64(0);
ulong ContextRecordPtr = Instance.WinHelper.GetArg64(1);

bool FirstChance = Instance.WinHelper.GetArg64(2) != 0;
bool FirstChance = (uint)Instance.WinHelper.GetArg64(2) != 0;
_ = FirstChance;

if (ExceptionRecordPtr == 0 || ContextRecordPtr == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
uint Count = (uint)Instance.WinHelper.GetArg64(0);
ulong HandlesPtr = Instance.WinHelper.GetArg64(1);
uint WaitType = (uint)Instance.WinHelper.GetArg64(2);
bool Alertable = Instance.WinHelper.GetArg64(3) != 0;
bool Alertable = (uint)Instance.WinHelper.GetArg64(3) != 0;
ulong TimeoutPtr = Instance.WinHelper.GetArg64(4);

EmulatedThread Thread = Instance.CurrentThread;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public NTSTATUS Handle(BinaryEmulator Instance)
return Instance.WinUnimplemented;

ulong Handle = Instance.WinHelper.GetArg64(0);
bool Alertable = Instance.WinHelper.GetArg64(1) != 0;
bool Alertable = (uint)Instance.WinHelper.GetArg64(1) != 0;
ulong TimeoutPtr = Instance.WinHelper.GetArg64(2);

EmulatedThread Thread = Instance.CurrentThread;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ public NTSTATUS Handle(BinaryEmulator Instance)
ulong BaseAddressPtr = Instance.WinHelper.GetArg64(1);
ulong ZeroBits = Instance.WinHelper.GetArg64(2); // ignored for now
ulong RegionSizePtr = Instance.WinHelper.GetArg64(3);
ulong AllocationTypeValue = Instance.WinHelper.GetArg64(4);
ulong ProtectValue = Instance.WinHelper.GetArg64(5);
ulong AllocationTypeValue = (uint)Instance.WinHelper.GetArg64(4);
ulong ProtectValue = (uint)Instance.WinHelper.GetArg64(5);
ulong RegionSize = 0;
if (ProcessHandle != ulong.MaxValue)
{
Expand Down Expand Up @@ -254,7 +254,29 @@ public NTSTATUS Handle(BinaryEmulator Instance)
ulong BaseAddress = BaseAddress32;

bool Reserve = (AllocationTypeValue & 0x2000U) != 0; // MEM_RESERVE
bool Commit = (AllocationTypeValue & 0x1000U) != 0; // MEM_COMMIT
bool Commit = (AllocationTypeValue & 0x1000U) != 0; // MEM_COMMIT

bool Reset = (AllocationTypeValue & MemReset) != 0;
bool ResetUndo = (AllocationTypeValue & MemResetUndo) != 0;
if (Reset || ResetUndo)
{
if ((Reset && ResetUndo) || (Reset && AllocationTypeValue != MemReset) || (ResetUndo && AllocationTypeValue != MemResetUndo))
return NTSTATUS.STATUS_INVALID_PARAMETER;

ulong ResetRegionSize = BinaryEmulator.AlignUp(RegionSizeRaw, PageSize);
if (!TryApplyResetState(Instance, BaseAddress, ResetRegionSize, Reset, out NTSTATUS ResetStatus))
return ResetStatus;

if (!Instance._emulator.WriteMemory(BaseAddressPtr, (uint)BaseAddress))
return NTSTATUS.STATUS_ACCESS_VIOLATION;

if (!Instance._emulator.WriteMemory(RegionSizePtr, (uint)ResetRegionSize))
return NTSTATUS.STATUS_ACCESS_VIOLATION;

return NTSTATUS.STATUS_SUCCESS;
}

RegionSize = BinaryEmulator.AlignUp(RegionSizeRaw, PageSize);

bool Reset = (AllocationTypeValue & MemReset) != 0;
bool ResetUndo = (AllocationTypeValue & MemResetUndo) != 0;
Expand Down Expand Up @@ -316,4 +338,4 @@ public NTSTATUS Handle(BinaryEmulator Instance)
}
}
}
}
}
Loading
Loading