aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Kernel/Common
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-12-18 03:33:36 -0200
committerGitHub <noreply@github.com>2018-12-18 03:33:36 -0200
commit0039bb639493b2d1e2764cae380311ba8e87704b (patch)
tree63a912a95c8261775c2acb8a5b9ca0f10ad4ae33 /Ryujinx.HLE/HOS/Kernel/Common
parent2534a7f10c627810e6e0272b4cc9758e90f733c1 (diff)
Refactor SVC handler (#540)
* Refactor SVC handler * Get rid of KernelErr * Split kernel code files into multiple folders
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel/Common')
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs7
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs42
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs147
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs35
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs143
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs137
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs32
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs72
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs13
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs128
10 files changed, 756 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs
new file mode 100644
index 00000000..473683ff
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ interface IKFutureSchedulerObject
+ {
+ void TimeUp();
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs
new file mode 100644
index 00000000..ddb0c71f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs
@@ -0,0 +1,42 @@
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class KAutoObject
+ {
+ protected Horizon System;
+
+ public KAutoObject(Horizon system)
+ {
+ System = system;
+ }
+
+ public virtual KernelResult SetName(string name)
+ {
+ if (!System.AutoObjectNames.TryAdd(name, this))
+ {
+ return KernelResult.InvalidState;
+ }
+
+ return KernelResult.Success;
+ }
+
+ public static KernelResult RemoveName(Horizon system, string name)
+ {
+ if (!system.AutoObjectNames.TryRemove(name, out _))
+ {
+ return KernelResult.NotFound;
+ }
+
+ return KernelResult.Success;
+ }
+
+ public static KAutoObject FindNamedObject(Horizon system, string name)
+ {
+ if (system.AutoObjectNames.TryGetValue(name, out KAutoObject obj))
+ {
+ return obj;
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs b/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs
new file mode 100644
index 00000000..01bba65f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs
@@ -0,0 +1,147 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class KResourceLimit
+ {
+ private const int Time10SecondsMs = 10000;
+
+ private long[] _current;
+ private long[] _limit;
+ private long[] _available;
+
+ private object _lockObj;
+
+ private LinkedList<KThread> _waitingThreads;
+
+ private int _waitingThreadsCount;
+
+ private Horizon _system;
+
+ public KResourceLimit(Horizon system)
+ {
+ _current = new long[(int)LimitableResource.Count];
+ _limit = new long[(int)LimitableResource.Count];
+ _available = new long[(int)LimitableResource.Count];
+
+ _lockObj = new object();
+
+ _waitingThreads = new LinkedList<KThread>();
+
+ _system = system;
+ }
+
+ public bool Reserve(LimitableResource resource, ulong amount)
+ {
+ return Reserve(resource, (long)amount);
+ }
+
+ public bool Reserve(LimitableResource resource, long amount)
+ {
+ return Reserve(resource, amount, KTimeManager.ConvertMillisecondsToNanoseconds(Time10SecondsMs));
+ }
+
+ public bool Reserve(LimitableResource resource, long amount, long timeout)
+ {
+ long endTimePoint = KTimeManager.ConvertNanosecondsToMilliseconds(timeout);
+
+ endTimePoint += PerformanceCounter.ElapsedMilliseconds;
+
+ bool success = false;
+
+ int index = GetIndex(resource);
+
+ lock (_lockObj)
+ {
+ long newCurrent = _current[index] + amount;
+
+ while (newCurrent > _limit[index] && _available[index] + amount <= _limit[index])
+ {
+ _waitingThreadsCount++;
+
+ KConditionVariable.Wait(_system, _waitingThreads, _lockObj, timeout);
+
+ _waitingThreadsCount--;
+
+ newCurrent = _current[index] + amount;
+
+ if (timeout >= 0 && PerformanceCounter.ElapsedMilliseconds > endTimePoint)
+ {
+ break;
+ }
+ }
+
+ if (newCurrent <= _limit[index])
+ {
+ _current[index] = newCurrent;
+
+ success = true;
+ }
+ }
+
+ return success;
+ }
+
+ public void Release(LimitableResource resource, ulong amount)
+ {
+ Release(resource, (long)amount);
+ }
+
+ public void Release(LimitableResource resource, long amount)
+ {
+ Release(resource, amount, amount);
+ }
+
+ private void Release(LimitableResource resource, long usedAmount, long availableAmount)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lockObj)
+ {
+ _current [index] -= usedAmount;
+ _available[index] -= availableAmount;
+
+ if (_waitingThreadsCount > 0)
+ {
+ KConditionVariable.NotifyAll(_system, _waitingThreads);
+ }
+ }
+ }
+
+ public long GetRemainingValue(LimitableResource resource)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lockObj)
+ {
+ return _limit[index] - _current[index];
+ }
+ }
+
+ public KernelResult SetLimitValue(LimitableResource resource, long limit)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lockObj)
+ {
+ if (_current[index] <= limit)
+ {
+ _limit[index] = limit;
+
+ return KernelResult.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidState;
+ }
+ }
+ }
+
+ private static int GetIndex(LimitableResource resource)
+ {
+ return (int)resource;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs
new file mode 100644
index 00000000..87e55312
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs
@@ -0,0 +1,35 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class KSynchronizationObject : KAutoObject
+ {
+ public LinkedList<KThread> WaitingThreads;
+
+ public KSynchronizationObject(Horizon system) : base(system)
+ {
+ WaitingThreads = new LinkedList<KThread>();
+ }
+
+ public LinkedListNode<KThread> AddWaitingThread(KThread thread)
+ {
+ return WaitingThreads.AddLast(thread);
+ }
+
+ public void RemoveWaitingThread(LinkedListNode<KThread> node)
+ {
+ WaitingThreads.Remove(node);
+ }
+
+ public virtual void Signal()
+ {
+ System.Synchronization.SignalObject(this);
+ }
+
+ public virtual bool IsSignaled()
+ {
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
new file mode 100644
index 00000000..f6a9e6f9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
@@ -0,0 +1,143 @@
+using Ryujinx.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class KTimeManager : IDisposable
+ {
+ private class WaitingObject
+ {
+ public IKFutureSchedulerObject Object { get; private set; }
+
+ public long TimePoint { get; private set; }
+
+ public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint)
+ {
+ Object = schedulerObj;
+ TimePoint = timePoint;
+ }
+ }
+
+ private List<WaitingObject> _waitingObjects;
+
+ private AutoResetEvent _waitEvent;
+
+ private bool _keepRunning;
+
+ public KTimeManager()
+ {
+ _waitingObjects = new List<WaitingObject>();
+
+ _keepRunning = true;
+
+ Thread work = new Thread(WaitAndCheckScheduledObjects);
+
+ work.Start();
+ }
+
+ public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout)
+ {
+ long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout);
+
+ lock (_waitingObjects)
+ {
+ _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
+ }
+
+ _waitEvent.Set();
+ }
+
+ public static long ConvertNanosecondsToMilliseconds(long time)
+ {
+ time /= 1000000;
+
+ if ((ulong)time > int.MaxValue)
+ {
+ return int.MaxValue;
+ }
+
+ return time;
+ }
+
+ public static long ConvertMillisecondsToNanoseconds(long time)
+ {
+ return time * 1000000;
+ }
+
+ public static long ConvertMillisecondsToTicks(long time)
+ {
+ return time * 19200;
+ }
+
+ public void UnscheduleFutureInvocation(IKFutureSchedulerObject Object)
+ {
+ lock (_waitingObjects)
+ {
+ _waitingObjects.RemoveAll(x => x.Object == Object);
+ }
+ }
+
+ private void WaitAndCheckScheduledObjects()
+ {
+ using (_waitEvent = new AutoResetEvent(false))
+ {
+ while (_keepRunning)
+ {
+ WaitingObject next;
+
+ lock (_waitingObjects)
+ {
+ next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
+ }
+
+ if (next != null)
+ {
+ long timePoint = PerformanceCounter.ElapsedMilliseconds;
+
+ if (next.TimePoint > timePoint)
+ {
+ _waitEvent.WaitOne((int)(next.TimePoint - timePoint));
+ }
+
+ bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint;
+
+ if (timeUp)
+ {
+ lock (_waitingObjects)
+ {
+ timeUp = _waitingObjects.Remove(next);
+ }
+ }
+
+ if (timeUp)
+ {
+ next.Object.TimeUp();
+ }
+ }
+ else
+ {
+ _waitEvent.WaitOne();
+ }
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _keepRunning = false;
+
+ _waitEvent?.Set();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs
new file mode 100644
index 00000000..2a2aa743
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs
@@ -0,0 +1,137 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ static class KernelInit
+ {
+ public static void InitializeResourceLimit(KResourceLimit resourceLimit)
+ {
+ void EnsureSuccess(KernelResult result)
+ {
+ if (result != KernelResult.Success)
+ {
+ throw new InvalidOperationException($"Unexpected result \"{result}\".");
+ }
+ }
+
+ int kernelMemoryCfg = 0;
+
+ long ramSize = GetRamSize(kernelMemoryCfg);
+
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Memory, ramSize));
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Thread, 800));
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Event, 700));
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200));
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Session, 900));
+
+ if (!resourceLimit.Reserve(LimitableResource.Memory, 0) ||
+ !resourceLimit.Reserve(LimitableResource.Memory, 0x60000))
+ {
+ throw new InvalidOperationException("Unexpected failure reserving memory on resource limit.");
+ }
+ }
+
+ public static KMemoryRegionManager[] GetMemoryRegions()
+ {
+ KMemoryArrange arrange = GetMemoryArrange();
+
+ return new KMemoryRegionManager[]
+ {
+ GetMemoryRegion(arrange.Application),
+ GetMemoryRegion(arrange.Applet),
+ GetMemoryRegion(arrange.Service),
+ GetMemoryRegion(arrange.NvServices)
+ };
+ }
+
+ private static KMemoryRegionManager GetMemoryRegion(KMemoryArrangeRegion region)
+ {
+ return new KMemoryRegionManager(region.Address, region.Size, region.EndAddr);
+ }
+
+ private static KMemoryArrange GetMemoryArrange()
+ {
+ int mcEmemCfg = 0x1000;
+
+ ulong ememApertureSize = (ulong)(mcEmemCfg & 0x3fff) << 20;
+
+ int kernelMemoryCfg = 0;
+
+ ulong ramSize = (ulong)GetRamSize(kernelMemoryCfg);
+
+ ulong ramPart0;
+ ulong ramPart1;
+
+ if (ramSize * 2 > ememApertureSize)
+ {
+ ramPart0 = ememApertureSize / 2;
+ ramPart1 = ememApertureSize / 2;
+ }
+ else
+ {
+ ramPart0 = ememApertureSize;
+ ramPart1 = 0;
+ }
+
+ int memoryArrange = 1;
+
+ ulong applicationRgSize;
+
+ switch (memoryArrange)
+ {
+ case 2: applicationRgSize = 0x80000000; break;
+ case 0x11:
+ case 0x21: applicationRgSize = 0x133400000; break;
+ default: applicationRgSize = 0xcd500000; break;
+ }
+
+ ulong appletRgSize;
+
+ switch (memoryArrange)
+ {
+ case 2: appletRgSize = 0x61200000; break;
+ case 3: appletRgSize = 0x1c000000; break;
+ case 0x11: appletRgSize = 0x23200000; break;
+ case 0x12:
+ case 0x21: appletRgSize = 0x89100000; break;
+ default: appletRgSize = 0x1fb00000; break;
+ }
+
+ KMemoryArrangeRegion serviceRg;
+ KMemoryArrangeRegion nvServicesRg;
+ KMemoryArrangeRegion appletRg;
+ KMemoryArrangeRegion applicationRg;
+
+ const ulong nvServicesRgSize = 0x29ba000;
+
+ ulong applicationRgEnd = DramMemoryMap.DramEnd; //- RamPart0;
+
+ applicationRg = new KMemoryArrangeRegion(applicationRgEnd - applicationRgSize, applicationRgSize);
+
+ ulong nvServicesRgEnd = applicationRg.Address - appletRgSize;
+
+ nvServicesRg = new KMemoryArrangeRegion(nvServicesRgEnd - nvServicesRgSize, nvServicesRgSize);
+ appletRg = new KMemoryArrangeRegion(nvServicesRgEnd, appletRgSize);
+
+ //Note: There is an extra region used by the kernel, however
+ //since we are doing HLE we are not going to use that memory, so give all
+ //the remaining memory space to services.
+ ulong serviceRgSize = nvServicesRg.Address - DramMemoryMap.SlabHeapEnd;
+
+ serviceRg = new KMemoryArrangeRegion(DramMemoryMap.SlabHeapEnd, serviceRgSize);
+
+ return new KMemoryArrange(serviceRg, nvServicesRg, appletRg, applicationRg);
+ }
+
+ private static long GetRamSize(int kernelMemoryCfg)
+ {
+ switch ((kernelMemoryCfg >> 16) & 3)
+ {
+ case 1: return 0x180000000;
+ case 2: return 0x200000000;
+ default: return 0x100000000;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs
new file mode 100644
index 00000000..cea24693
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs
@@ -0,0 +1,32 @@
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ enum KernelResult
+ {
+ Success = 0,
+ InvalidCapability = 0x1c01,
+ ThreadNotStarted = 0x7201,
+ ThreadTerminating = 0x7601,
+ InvalidSize = 0xca01,
+ InvalidAddress = 0xcc01,
+ OutOfResource = 0xce01,
+ OutOfMemory = 0xd001,
+ HandleTableFull = 0xd201,
+ InvalidMemState = 0xd401,
+ InvalidPermission = 0xd801,
+ InvalidMemRange = 0xdc01,
+ InvalidPriority = 0xe001,
+ InvalidCpuCore = 0xe201,
+ InvalidHandle = 0xe401,
+ UserCopyFailed = 0xe601,
+ InvalidCombination = 0xe801,
+ TimedOut = 0xea01,
+ Cancelled = 0xec01,
+ MaximumExceeded = 0xee01,
+ InvalidEnumValue = 0xf001,
+ NotFound = 0xf201,
+ InvalidThread = 0xf401,
+ InvalidState = 0xfa01,
+ ReservedValue = 0xfc01,
+ ResLimitExceeded = 0x10801
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs
new file mode 100644
index 00000000..a29b1722
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs
@@ -0,0 +1,72 @@
+using Ryujinx.HLE.HOS.Kernel.Process;
+using ChocolArm64.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ static class KernelTransfer
+ {
+ public static bool UserToKernelInt32(Horizon system, ulong address, out int value)
+ {
+ KProcess currentProcess = system.Scheduler.GetCurrentProcess();
+
+ if (currentProcess.CpuMemory.IsMapped((long)address) &&
+ currentProcess.CpuMemory.IsMapped((long)address + 3))
+ {
+ value = currentProcess.CpuMemory.ReadInt32((long)address);
+
+ return true;
+ }
+
+ value = 0;
+
+ return false;
+ }
+
+ public static bool UserToKernelString(Horizon system, ulong address, int size, out string value)
+ {
+ KProcess currentProcess = system.Scheduler.GetCurrentProcess();
+
+ if (currentProcess.CpuMemory.IsMapped((long)address) &&
+ currentProcess.CpuMemory.IsMapped((long)address + size - 1))
+ {
+ value = MemoryHelper.ReadAsciiString(currentProcess.CpuMemory, (long)address, size);
+
+ return true;
+ }
+
+ value = null;
+
+ return false;
+ }
+
+ public static bool KernelToUserInt32(Horizon system, ulong address, int value)
+ {
+ KProcess currentProcess = system.Scheduler.GetCurrentProcess();
+
+ if (currentProcess.CpuMemory.IsMapped((long)address) &&
+ currentProcess.CpuMemory.IsMapped((long)address + 3))
+ {
+ currentProcess.CpuMemory.WriteInt32ToSharedAddr((long)address, value);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool KernelToUserInt64(Horizon system, ulong address, long value)
+ {
+ KProcess currentProcess = system.Scheduler.GetCurrentProcess();
+
+ if (currentProcess.CpuMemory.IsMapped((long)address) &&
+ currentProcess.CpuMemory.IsMapped((long)address + 7))
+ {
+ currentProcess.CpuMemory.WriteInt64((long)address, value);
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs b/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs
new file mode 100644
index 00000000..2e6a3e45
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ enum LimitableResource : byte
+ {
+ Memory = 0,
+ Thread = 1,
+ Event = 2,
+ TransferMemory = 3,
+ Session = 4,
+
+ Count = 5
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs b/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs
new file mode 100644
index 00000000..6b686901
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs
@@ -0,0 +1,128 @@
+using Ryujinx.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class MersenneTwister
+ {
+ private int _index;
+ private uint[] _mt;
+
+ public MersenneTwister(uint seed)
+ {
+ _mt = new uint[624];
+
+ _mt[0] = seed;
+
+ for (int mtIdx = 1; mtIdx < _mt.Length; mtIdx++)
+ {
+ uint prev = _mt[mtIdx - 1];
+
+ _mt[mtIdx] = (uint)(0x6c078965 * (prev ^ (prev >> 30)) + mtIdx);
+ }
+
+ _index = _mt.Length;
+ }
+
+ public long GenRandomNumber(long min, long max)
+ {
+ long range = max - min;
+
+ if (min == max)
+ {
+ return min;
+ }
+
+ if (range == -1)
+ {
+ //Increment would cause a overflow, special case.
+ return GenRandomNumber(2, 2, 32, 0xffffffffu, 0xffffffffu);
+ }
+
+ range++;
+
+ //This is log2(Range) plus one.
+ int nextRangeLog2 = 64 - BitUtils.CountLeadingZeros64(range);
+
+ //If Range is already power of 2, subtract one to use log2(Range) directly.
+ int rangeLog2 = nextRangeLog2 - (BitUtils.IsPowerOfTwo64(range) ? 1 : 0);
+
+ int parts = rangeLog2 > 32 ? 2 : 1;
+ int bitsPerPart = rangeLog2 / parts;
+
+ int fullParts = parts - (rangeLog2 - parts * bitsPerPart);
+
+ uint mask = 0xffffffffu >> (32 - bitsPerPart);
+ uint maskPlus1 = 0xffffffffu >> (31 - bitsPerPart);
+
+ long randomNumber;
+
+ do
+ {
+ randomNumber = GenRandomNumber(parts, fullParts, bitsPerPart, mask, maskPlus1);
+ }
+ while ((ulong)randomNumber >= (ulong)range);
+
+ return min + randomNumber;
+ }
+
+ private long GenRandomNumber(
+ int parts,
+ int fullParts,
+ int bitsPerPart,
+ uint mask,
+ uint maskPlus1)
+ {
+ long randomNumber = 0;
+
+ int part = 0;
+
+ for (; part < fullParts; part++)
+ {
+ randomNumber <<= bitsPerPart;
+ randomNumber |= GenRandomNumber() & mask;
+ }
+
+ for (; part < parts; part++)
+ {
+ randomNumber <<= bitsPerPart + 1;
+ randomNumber |= GenRandomNumber() & maskPlus1;
+ }
+
+ return randomNumber;
+ }
+
+ private uint GenRandomNumber()
+ {
+ if (_index >= _mt.Length)
+ {
+ Twist();
+ }
+
+ uint value = _mt[_index++];
+
+ value ^= value >> 11;
+ value ^= (value << 7) & 0x9d2c5680;
+ value ^= (value << 15) & 0xefc60000;
+ value ^= value >> 18;
+
+ return value;
+ }
+
+ private void Twist()
+ {
+ for (int mtIdx = 0; mtIdx < _mt.Length; mtIdx++)
+ {
+ uint value = (_mt[mtIdx] & 0x80000000) + (_mt[(mtIdx + 1) % _mt.Length] & 0x7fffffff);
+
+ _mt[mtIdx] = _mt[(mtIdx + 397) % _mt.Length] ^ (value >> 1);
+
+ if ((value & 1) != 0)
+ {
+ _mt[mtIdx] ^= 0x9908b0df;
+ }
+ }
+
+ _index = 0;
+ }
+ }
+}