Add checking for memory block slab heap usage, return errors if full, exit gracefully

This commit is contained in:
gdkchan 2018-11-21 18:10:49 -03:00
parent bba8b4dbab
commit dbcba511b2
10 changed files with 301 additions and 52 deletions

View file

@ -39,6 +39,9 @@ namespace Ryujinx.HLE.HOS
internal KMemoryRegionManager[] MemoryRegions { get; private set; }
internal KMemoryBlockAllocator MemoryBlockAllocatorSys { get; private set; }
internal KMemoryBlockAllocator MemoryBlockAllocator2 { get; private set; }
internal KSlabHeap UserSlabHeapPages { get; private set; }
internal KCriticalSection CriticalSection { get; private set; }
@ -55,6 +58,10 @@ namespace Ryujinx.HLE.HOS
private long ProcessId;
private long ThreadUid;
internal CountdownEvent ThreadCounter;
internal LinkedList<KProcess> Processes;
internal AppletStateMgr AppletState { get; private set; }
internal KSharedMemory HidSharedMem { get; private set; }
@ -90,6 +97,9 @@ namespace Ryujinx.HLE.HOS
MemoryRegions = KernelInit.GetMemoryRegions();
MemoryBlockAllocatorSys = new KMemoryBlockAllocator(0x4e20000);
MemoryBlockAllocator2 = new KMemoryBlockAllocator(0x2710000);
UserSlabHeapPages = new KSlabHeap(
UserSlabHeapBase,
UserSlabHeapItemSize,
@ -112,6 +122,10 @@ namespace Ryujinx.HLE.HOS
KernelInitialized = true;
ThreadCounter = new CountdownEvent(1);
Processes = new LinkedList<KProcess>();
KMemoryRegionManager Region = MemoryRegions[(int)MemoryRegion.Service];
ulong HidPa = Region.Address;
@ -571,6 +585,17 @@ namespace Ryujinx.HLE.HOS
{
if (Disposing)
{
//Force all threads to exit.
foreach (KProcess Process in Processes)
{
Process.StopAllThreads();
}
//It's only safe to release resources once all threads
//have exited.
ThreadCounter.Signal();
ThreadCounter.Wait();
Scheduler.Dispose();
TimeManager.Dispose();

View file

@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel
public bool MultiCoreScheduling { get; set; }
private HleCoreManager CoreManager;
public HleCoreManager CoreManager { get; private set; }
private bool KeepPreempting;

View file

@ -0,0 +1,21 @@
namespace Ryujinx.HLE.HOS.Kernel
{
class KMemoryBlockAllocator
{
private const int KMemoryBlockSize = 0x40;
private ulong Size;
public int Count { get; set; }
public KMemoryBlockAllocator(ulong Size)
{
this.Size = Size;
}
public bool CanAllocate(int Count)
{
return (ulong)(this.Count + Count) * KMemoryBlockSize <= Size;
}
}
}

View file

@ -3,15 +3,17 @@ using Ryujinx.Common;
using System;
using System.Collections.Generic;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Kernel
{
class KMemoryManager
{
public const int PageSize = 0x1000;
private const int KMemoryBlockSize = 0x40;
public const int KMemoryBlockSize = 0x40;
//We need 2 blocks for the case where a big block
//needs to be split in 2, plus one block that will be the new one inserted.
private const int MaxBlocksNeededForInsertion = 2;
private LinkedList<KMemoryBlock> Blocks;
@ -41,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel
private ulong HeapCapacity;
public ulong PersonalMmHeapUsage { get; private set; }
public ulong PhysicalMemoryUsage { get; private set; }
private MemoryRegion MemRegion;
@ -52,6 +54,8 @@ namespace Ryujinx.HLE.HOS.Kernel
private bool IsKernel;
private bool AslrEnabled;
private KMemoryBlockAllocator BlockAllocator;
private int ContextId;
private MersenneTwister RandomNumberGenerator;
@ -67,12 +71,13 @@ namespace Ryujinx.HLE.HOS.Kernel
private static readonly int[] AddrSpaceSizes = new int[] { 32, 36, 32, 39 };
public KernelResult InitializeForProcess(
AddressSpaceType AddrSpaceType,
bool AslrEnabled,
bool AslrDisabled,
MemoryRegion MemRegion,
ulong Address,
ulong Size)
AddressSpaceType AddrSpaceType,
bool AslrEnabled,
bool AslrDisabled,
MemoryRegion MemRegion,
ulong Address,
ulong Size,
KMemoryBlockAllocator BlockAllocator)
{
if ((uint)AddrSpaceType > (uint)AddressSpaceType.Addr39Bits)
{
@ -92,7 +97,8 @@ namespace Ryujinx.HLE.HOS.Kernel
AddrSpaceSize,
MemRegion,
Address,
Size);
Size,
BlockAllocator);
if (Result != KernelResult.Success)
{
@ -111,14 +117,15 @@ namespace Ryujinx.HLE.HOS.Kernel
}
private KernelResult CreateUserAddressSpace(
AddressSpaceType AddrSpaceType,
bool AslrEnabled,
bool AslrDisabled,
ulong AddrSpaceStart,
ulong AddrSpaceEnd,
MemoryRegion MemRegion,
ulong Address,
ulong Size)
AddressSpaceType AddrSpaceType,
bool AslrEnabled,
bool AslrDisabled,
ulong AddrSpaceStart,
ulong AddrSpaceEnd,
MemoryRegion MemRegion,
ulong Address,
ulong Size,
KMemoryBlockAllocator BlockAllocator)
{
ulong EndAddr = Address + Size;
@ -211,9 +218,13 @@ namespace Ryujinx.HLE.HOS.Kernel
ulong AslrMaxOffset = MapAvailableSize - MapTotalSize;
this.AslrEnabled = AslrEnabled;
this.AddrSpaceStart = AddrSpaceStart;
this.AddrSpaceEnd = AddrSpaceEnd;
this.BlockAllocator = BlockAllocator;
if (MapAvailableSize < MapTotalSize)
{
return KernelResult.OutOfMemory;
@ -274,14 +285,12 @@ namespace Ryujinx.HLE.HOS.Kernel
CurrentHeapAddr = HeapRegionStart;
HeapCapacity = 0;
PersonalMmHeapUsage = 0;
PhysicalMemoryUsage = 0;
this.MemRegion = MemRegion;
this.AslrDisabled = AslrDisabled;
InitializeBlocks(AddrSpaceStart, AddrSpaceEnd);
return KernelResult.Success;
return InitializeBlocks(AddrSpaceStart, AddrSpaceEnd);
}
private ulong GetRandomValue(ulong Min, ulong Max)
@ -313,11 +322,20 @@ namespace Ryujinx.HLE.HOS.Kernel
}
}
private void InitializeBlocks(ulong AddrSpaceStart, ulong AddrSpaceEnd)
private KernelResult InitializeBlocks(ulong AddrSpaceStart, ulong AddrSpaceEnd)
{
//First insertion will always need only a single block,
//because there's nothing else to split.
if (!BlockAllocator.CanAllocate(1))
{
return KernelResult.OutOfResource;
}
ulong AddrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize;
InsertBlock(AddrSpaceStart, AddrSpacePagesCount, MemoryState.Unmapped);
return KernelResult.Success;
}
public KernelResult MapPages(
@ -342,6 +360,11 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.InvalidMemState;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
KernelResult Result = MapPages(Address, PageList, Permission);
if (Result == KernelResult.Success)
@ -403,6 +426,11 @@ namespace Ryujinx.HLE.HOS.Kernel
out _,
out _))
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
KernelResult Result = MmuUnmap(Address, PagesCount);
if (Result == KernelResult.Success)
@ -525,6 +553,11 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.OutOfMemory;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
MemoryOperation Operation = Map
? MemoryOperation.MapPa
: MemoryOperation.Allocate;
@ -568,6 +601,11 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.InvalidMemState;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
KernelResult Result = DoMmuOperation(
Address,
PagesCount,
@ -609,6 +647,11 @@ namespace Ryujinx.HLE.HOS.Kernel
if (Success)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2))
{
return KernelResult.OutOfResource;
}
KPageList PageList = new KPageList();
AddVaRangeToPageList(PageList, Src, PagesCount);
@ -684,6 +727,13 @@ namespace Ryujinx.HLE.HOS.Kernel
return Result;
}
//TODO: Missing some checks here.
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2))
{
return KernelResult.OutOfResource;
}
InsertBlock(Dst, PagesCount, MemoryState.Unmapped);
InsertBlock(Src, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite);
@ -705,6 +755,8 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.OutOfMemory;
}
KProcess CurrentProcess = System.Scheduler.GetCurrentProcess();
ulong CurrentHeapSize = GetHeapSize();
if (CurrentHeapSize <= Size)
@ -714,19 +766,49 @@ namespace Ryujinx.HLE.HOS.Kernel
lock (Blocks)
{
if (CurrentProcess.ResourceLimit != null && DiffSize != 0 &&
!CurrentProcess.ResourceLimit.Reserve(LimitableResource.Memory, DiffSize))
{
return KernelResult.ResLimitExceeded;
}
ulong PagesCount = DiffSize / PageSize;
KMemoryRegionManager Region = GetMemoryRegionManager();
KernelResult Result = Region.AllocatePages(PagesCount, AslrDisabled, out KPageList PageList);
void CleanUpForError()
{
if (PageList != null)
{
Region.FreePages(PageList);
}
if (CurrentProcess.ResourceLimit != null && DiffSize != 0)
{
CurrentProcess.ResourceLimit.Release(LimitableResource.Memory, DiffSize);
}
}
if (Result != KernelResult.Success)
{
CleanUpForError();
return Result;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
CleanUpForError();
return KernelResult.OutOfResource;
}
if (!IsUnmapped(CurrentHeapAddr, DiffSize))
{
CleanUpForError();
return KernelResult.InvalidMemState;
}
@ -739,6 +821,8 @@ namespace Ryujinx.HLE.HOS.Kernel
if (Result != KernelResult.Success)
{
CleanUpForError();
return Result;
}
@ -753,6 +837,11 @@ namespace Ryujinx.HLE.HOS.Kernel
lock (Blocks)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
if (!CheckRange(
FreeAddr,
DiffSize,
@ -779,6 +868,8 @@ namespace Ryujinx.HLE.HOS.Kernel
return Result;
}
CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, BitUtils.AlignDown(DiffSize, PageSize));
InsertBlock(FreeAddr, PagesCount, MemoryState.Unmapped);
}
}
@ -794,7 +885,7 @@ namespace Ryujinx.HLE.HOS.Kernel
{
lock (Blocks)
{
return GetHeapSize() + PersonalMmHeapUsage;
return GetHeapSize() + PhysicalMemoryUsage;
}
}
@ -813,7 +904,7 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.Success;
}
public long SetMemoryAttribute(
public KernelResult SetMemoryAttribute(
ulong Address,
ulong Size,
MemoryAttribute AttributeMask,
@ -835,6 +926,11 @@ namespace Ryujinx.HLE.HOS.Kernel
out MemoryPermission Permission,
out MemoryAttribute Attribute))
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
Attribute &= ~AttributeMask;
@ -842,11 +938,13 @@ namespace Ryujinx.HLE.HOS.Kernel
InsertBlock(Address, PagesCount, State, Permission, Attribute);
return 0;
return KernelResult.Success;
}
else
{
return KernelResult.InvalidMemState;
}
}
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
}
public KMemoryInfo QueryMemory(ulong Address)
@ -896,6 +994,11 @@ namespace Ryujinx.HLE.HOS.Kernel
if (Success)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
KPageList PageList = new KPageList();
@ -953,6 +1056,11 @@ namespace Ryujinx.HLE.HOS.Kernel
out _,
out _))
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
KernelResult Result = MmuUnmap(Address, PagesCount);
if (Result == KernelResult.Success)
@ -1005,6 +1113,11 @@ namespace Ryujinx.HLE.HOS.Kernel
if (Success)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
KPageList SrcPageList = new KPageList();
@ -1046,7 +1159,7 @@ namespace Ryujinx.HLE.HOS.Kernel
}
}
public long ReserveTransferMemory(ulong Address, ulong Size, MemoryPermission Permission)
public KernelResult ReserveTransferMemory(ulong Address, ulong Size, MemoryPermission Permission)
{
lock (Blocks)
{
@ -1064,20 +1177,29 @@ namespace Ryujinx.HLE.HOS.Kernel
out _,
out MemoryAttribute Attribute))
{
//TODO: Missing checks.
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
Attribute |= MemoryAttribute.Borrowed;
InsertBlock(Address, PagesCount, State, Permission, Attribute);
return 0;
return KernelResult.Success;
}
else
{
return KernelResult.InvalidMemState;
}
}
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
}
public long ResetTransferMemory(ulong Address, ulong Size)
public KernelResult ResetTransferMemory(ulong Address, ulong Size)
{
lock (Blocks)
{
@ -1095,15 +1217,22 @@ namespace Ryujinx.HLE.HOS.Kernel
out _,
out _))
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
InsertBlock(Address, PagesCount, State, MemoryPermission.ReadAndWrite);
return 0;
return KernelResult.Success;
}
else
{
return KernelResult.InvalidMemState;
}
}
return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
}
public KernelResult SetProcessMemoryPermission(ulong Address, ulong Size, MemoryPermission Permission)
@ -1146,6 +1275,11 @@ namespace Ryujinx.HLE.HOS.Kernel
if (NewState != OldState || Permission != OldPermission)
{
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
ulong PagesCount = Size / PageSize;
MemoryOperation Operation = (Permission & MemoryPermission.Execute) != 0
@ -1217,16 +1351,33 @@ namespace Ryujinx.HLE.HOS.Kernel
KernelResult Result = Region.AllocatePages(RemainingPages, AslrDisabled, out KPageList PageList);
void CleanUpForError()
{
if (PageList != null)
{
Region.FreePages(PageList);
}
CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, RemainingSize);
}
if (Result != KernelResult.Success)
{
CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, RemainingSize);
CleanUpForError();
return Result;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
CleanUpForError();
return KernelResult.OutOfResource;
}
MapPhysicalMemory(PageList, Address, EndAddr);
PersonalMmHeapUsage += RemainingSize;
PhysicalMemoryUsage += RemainingSize;
ulong PagesCount = Size / PageSize;
@ -1294,6 +1445,11 @@ namespace Ryujinx.HLE.HOS.Kernel
return KernelResult.Success;
}
if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion))
{
return KernelResult.OutOfResource;
}
//Try to unmap all the heap mapped memory inside range.
KernelResult Result = KernelResult.Success;
@ -1329,7 +1485,7 @@ namespace Ryujinx.HLE.HOS.Kernel
{
GetMemoryRegionManager().FreePages(PageList);
PersonalMmHeapUsage -= HeapMappedSize;
PhysicalMemoryUsage -= HeapMappedSize;
KProcess CurrentProcess = System.Scheduler.GetCurrentProcess();
@ -1545,6 +1701,8 @@ namespace Ryujinx.HLE.HOS.Kernel
//Insert new block on the list only on areas where the state
//of the block matches the state specified on the Old* state
//arguments, otherwise leave it as is.
int OldCount = Blocks.Count;
OldAttribute |= MemoryAttribute.IpcAndDeviceMapped;
ulong EndAddr = PagesCount * PageSize + BaseAddress;
@ -1637,6 +1795,8 @@ namespace Ryujinx.HLE.HOS.Kernel
Node = NextNode;
}
BlockAllocator.Count += Blocks.Count - OldCount;
}
private void InsertBlock(
@ -1650,6 +1810,8 @@ namespace Ryujinx.HLE.HOS.Kernel
//existing blocks as needed.
KMemoryBlock Block = new KMemoryBlock(BaseAddress, PagesCount, State, Permission, Attribute);
int OldCount = Blocks.Count;
ulong EndAddr = PagesCount * PageSize + BaseAddress;
LinkedListNode<KMemoryBlock> NewNode = null;
@ -1729,6 +1891,8 @@ namespace Ryujinx.HLE.HOS.Kernel
}
MergeEqualStateNeighbours(NewNode);
BlockAllocator.Count += Blocks.Count - OldCount;
}
private void MergeEqualStateNeighbours(LinkedListNode<KMemoryBlock> Node)

View file

@ -288,6 +288,8 @@ namespace Ryujinx.HLE.HOS.Kernel
FreePages(PageNode.Address, PageNode.PagesCount);
}
PageList = null;
return KernelResult.OutOfMemory;
}

View file

@ -124,13 +124,18 @@ namespace Ryujinx.HLE.HOS.Kernel
ulong CodeSize = (ulong)CreationInfo.CodePagesCount * KMemoryManager.PageSize;
KMemoryBlockAllocator MemoryBlockAllocator = (MmuFlags & 0x40) != 0
? System.MemoryBlockAllocatorSys
: System.MemoryBlockAllocator2;
KernelResult Result = MemoryManager.InitializeForProcess(
AddrSpaceType,
AslrEnabled,
!AslrEnabled,
MemRegion,
CodeAddress,
CodeSize);
CodeSize,
MemoryBlockAllocator);
if (Result != KernelResult.Success)
{
@ -205,9 +210,17 @@ namespace Ryujinx.HLE.HOS.Kernel
PersonalMmHeapPagesCount = (ulong)CreationInfo.PersonalMmHeapPagesCount;
KMemoryBlockAllocator MemoryBlockAllocator;
if (PersonalMmHeapPagesCount != 0)
{
MemoryBlockAllocator = new KMemoryBlockAllocator(PersonalMmHeapPagesCount * KMemoryManager.PageSize);
}
else
{
MemoryBlockAllocator = (MmuFlags & 0x40) != 0
? System.MemoryBlockAllocatorSys
: System.MemoryBlockAllocator2;
}
AddressSpaceType AddrSpaceType = (AddressSpaceType)((CreationInfo.MmuFlags >> 1) & 7);
@ -224,7 +237,8 @@ namespace Ryujinx.HLE.HOS.Kernel
!AslrEnabled,
MemRegion,
CodeAddress,
CodeSize);
CodeSize,
MemoryBlockAllocator);
if (Result != KernelResult.Success)
{
@ -314,8 +328,8 @@ namespace Ryujinx.HLE.HOS.Kernel
return false;
}
if (MemoryManager.InsideHeapRegion(Address, Size) ||
MemoryManager.InsideAliasRegion (Address, Size))
if (MemoryManager.InsideHeapRegion (Address, Size) ||
MemoryManager.InsideAliasRegion(Address, Size))
{
return false;
}
@ -771,10 +785,14 @@ namespace Ryujinx.HLE.HOS.Kernel
public void IncrementThreadCount()
{
Interlocked.Increment(ref ThreadCount);
System.ThreadCounter.AddCount();
}
public void DecrementThreadCountAndTerminateIfZero()
{
System.ThreadCounter.Signal();
if (Interlocked.Decrement(ref ThreadCount) == 0)
{
Terminate();
@ -801,8 +819,7 @@ namespace Ryujinx.HLE.HOS.Kernel
public ulong GetMemoryUsage()
{
//TODO: Personal Mm Heap.
return ImageSize + MainThreadStackSize + MemoryManager.GetTotalHeapSize();
return ImageSize + MainThreadStackSize + MemoryManager.GetTotalHeapSize() + GetPersonalMmHeapSize();
}
public ulong GetMemoryCapacityWithoutPersonalMmHeap()
@ -895,7 +912,7 @@ namespace Ryujinx.HLE.HOS.Kernel
if (ShallTerminate)
{
UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread());
//UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread());
HandleTable.Destroy();
@ -955,6 +972,19 @@ namespace Ryujinx.HLE.HOS.Kernel
return Result;
}
public void StopAllThreads()
{
lock (ThreadingLock)
{
foreach (KThread Thread in Threads)
{
Thread.Context.StopExecution();
System.Scheduler.CoreManager.Set(Thread.Context.Work);
}
}
}
private void CpuTraceHandler(object sender, EventArgs e)
{
System.Scheduler.GetCurrentThread().PrintGuestStackTrace();

View file

@ -7,6 +7,7 @@ namespace Ryujinx.HLE.HOS.Kernel
ThreadTerminating = 0x7601,
InvalidSize = 0xca01,
InvalidAddress = 0xcc01,
OutOfResource = 0xce01,
OutOfMemory = 0xd001,
HandleTableFull = 0xd201,
InvalidMemState = 0xd401,

View file

@ -72,15 +72,15 @@ namespace Ryujinx.HLE.HOS.Kernel
return;
}
long Result = Process.MemoryManager.SetMemoryAttribute(
KernelResult Result = Process.MemoryManager.SetMemoryAttribute(
Position,
Size,
AttributeMask,
AttributeValue);
if (Result != 0)
if (Result != KernelResult.Success)
{
Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\".");
}
else
{

View file

@ -221,6 +221,8 @@ namespace Ryujinx.HLE.HOS.Kernel
Message,
MessagePtr));
System.ThreadCounter.AddCount();
System.CriticalSection.Leave();
ThreadState.X0 = (ulong)CurrentThread.ObjSyncResult;
@ -245,6 +247,8 @@ namespace Ryujinx.HLE.HOS.Kernel
IpcMessage.Message,
IpcMessage.MessagePtr);
System.ThreadCounter.Signal();
IpcMessage.Thread.Reschedule(ThreadSchedState.Running);
}

View file

@ -151,6 +151,8 @@ namespace Ryujinx.HLE.HOS
return false;
}
System.Processes.AddLast(Process);
return true;
}
}