Merge branch 'master' into ICommonStateGetter
This commit is contained in:
commit
c636c74dd2
248 changed files with 2266 additions and 2244 deletions
59
Ryujinx.HLE/Gpu/BlockLinearSwizzle.cs
Normal file
59
Ryujinx.HLE/Gpu/BlockLinearSwizzle.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class BlockLinearSwizzle : ISwizzle
|
||||
{
|
||||
private int BhShift;
|
||||
private int BppShift;
|
||||
private int BhMask;
|
||||
|
||||
private int XShift;
|
||||
private int GobStride;
|
||||
|
||||
public BlockLinearSwizzle(int Width, int Bpp, int BlockHeight = 16)
|
||||
{
|
||||
BhMask = (BlockHeight * 8) - 1;
|
||||
|
||||
BhShift = CountLsbZeros(BlockHeight * 8);
|
||||
BppShift = CountLsbZeros(Bpp);
|
||||
|
||||
int WidthInGobs = (int)MathF.Ceiling(Width * Bpp / 64f);
|
||||
|
||||
GobStride = 512 * BlockHeight * WidthInGobs;
|
||||
|
||||
XShift = CountLsbZeros(512 * BlockHeight);
|
||||
}
|
||||
|
||||
private int CountLsbZeros(int Value)
|
||||
{
|
||||
int Count = 0;
|
||||
|
||||
while (((Value >> Count) & 1) == 0)
|
||||
{
|
||||
Count++;
|
||||
}
|
||||
|
||||
return Count;
|
||||
}
|
||||
|
||||
public int GetSwizzleOffset(int X, int Y)
|
||||
{
|
||||
X <<= BppShift;
|
||||
|
||||
int Position = (Y >> BhShift) * GobStride;
|
||||
|
||||
Position += (X >> 6) << XShift;
|
||||
|
||||
Position += ((Y & BhMask) >> 3) << 9;
|
||||
|
||||
Position += ((X & 0x3f) >> 5) << 8;
|
||||
Position += ((Y & 0x07) >> 1) << 6;
|
||||
Position += ((X & 0x1f) >> 4) << 5;
|
||||
Position += ((Y & 0x01) >> 0) << 4;
|
||||
Position += ((X & 0x0f) >> 0) << 0;
|
||||
|
||||
return Position;
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/Gpu/INvGpuEngine.cs
Normal file
9
Ryujinx.HLE/Gpu/INvGpuEngine.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
interface INvGpuEngine
|
||||
{
|
||||
int[] Registers { get; }
|
||||
|
||||
void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry);
|
||||
}
|
||||
}
|
7
Ryujinx.HLE/Gpu/ISwizzle.cs
Normal file
7
Ryujinx.HLE/Gpu/ISwizzle.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
interface ISwizzle
|
||||
{
|
||||
int GetSwizzleOffset(int X, int Y);
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/Gpu/LinearSwizzle.cs
Normal file
19
Ryujinx.HLE/Gpu/LinearSwizzle.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class LinearSwizzle : ISwizzle
|
||||
{
|
||||
private int Pitch;
|
||||
private int Bpp;
|
||||
|
||||
public LinearSwizzle(int Pitch, int Bpp)
|
||||
{
|
||||
this.Pitch = Pitch;
|
||||
this.Bpp = Bpp;
|
||||
}
|
||||
|
||||
public int GetSwizzleOffset(int X, int Y)
|
||||
{
|
||||
return X * Bpp + Y * Pitch;
|
||||
}
|
||||
}
|
||||
}
|
419
Ryujinx.HLE/Gpu/MacroInterpreter.cs
Normal file
419
Ryujinx.HLE/Gpu/MacroInterpreter.cs
Normal file
|
@ -0,0 +1,419 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class MacroInterpreter
|
||||
{
|
||||
private enum AssignmentOperation
|
||||
{
|
||||
IgnoreAndFetch = 0,
|
||||
Move = 1,
|
||||
MoveAndSetMaddr = 2,
|
||||
FetchAndSend = 3,
|
||||
MoveAndSend = 4,
|
||||
FetchAndSetMaddr = 5,
|
||||
MoveAndSetMaddrThenFetchAndSend = 6,
|
||||
MoveAndSetMaddrThenSendHigh = 7
|
||||
}
|
||||
|
||||
private enum AluOperation
|
||||
{
|
||||
AluReg = 0,
|
||||
AddImmediate = 1,
|
||||
BitfieldReplace = 2,
|
||||
BitfieldExtractLslImm = 3,
|
||||
BitfieldExtractLslReg = 4,
|
||||
ReadImmediate = 5
|
||||
}
|
||||
|
||||
private enum AluRegOperation
|
||||
{
|
||||
Add = 0,
|
||||
AddWithCarry = 1,
|
||||
Subtract = 2,
|
||||
SubtractWithBorrow = 3,
|
||||
BitwiseExclusiveOr = 8,
|
||||
BitwiseOr = 9,
|
||||
BitwiseAnd = 10,
|
||||
BitwiseAndNot = 11,
|
||||
BitwiseNotAnd = 12
|
||||
}
|
||||
|
||||
private NvGpuFifo PFifo;
|
||||
private INvGpuEngine Engine;
|
||||
|
||||
public Queue<int> Fifo { get; private set; }
|
||||
|
||||
private int[] Gprs;
|
||||
|
||||
private int MethAddr;
|
||||
private int MethIncr;
|
||||
|
||||
private bool Carry;
|
||||
|
||||
private int OpCode;
|
||||
|
||||
private int PipeOp;
|
||||
|
||||
private long Pc;
|
||||
|
||||
public MacroInterpreter(NvGpuFifo PFifo, INvGpuEngine Engine)
|
||||
{
|
||||
this.PFifo = PFifo;
|
||||
this.Engine = Engine;
|
||||
|
||||
Fifo = new Queue<int>();
|
||||
|
||||
Gprs = new int[8];
|
||||
}
|
||||
|
||||
public void Execute(NvGpuVmm Vmm, long Position, int Param)
|
||||
{
|
||||
Reset();
|
||||
|
||||
Gprs[1] = Param;
|
||||
|
||||
Pc = Position;
|
||||
|
||||
FetchOpCode(Vmm);
|
||||
|
||||
while (Step(Vmm));
|
||||
|
||||
//Due to the delay slot, we still need to execute
|
||||
//one more instruction before we actually exit.
|
||||
Step(Vmm);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
for (int Index = 0; Index < Gprs.Length; Index++)
|
||||
{
|
||||
Gprs[Index] = 0;
|
||||
}
|
||||
|
||||
MethAddr = 0;
|
||||
MethIncr = 0;
|
||||
|
||||
Carry = false;
|
||||
}
|
||||
|
||||
private bool Step(NvGpuVmm Vmm)
|
||||
{
|
||||
long BaseAddr = Pc - 4;
|
||||
|
||||
FetchOpCode(Vmm);
|
||||
|
||||
if ((OpCode & 7) < 7)
|
||||
{
|
||||
//Operation produces a value.
|
||||
AssignmentOperation AsgOp = (AssignmentOperation)((OpCode >> 4) & 7);
|
||||
|
||||
int Result = GetAluResult();
|
||||
|
||||
switch (AsgOp)
|
||||
{
|
||||
//Fetch parameter and ignore result.
|
||||
case AssignmentOperation.IgnoreAndFetch:
|
||||
{
|
||||
SetDstGpr(FetchParam());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Move result.
|
||||
case AssignmentOperation.Move:
|
||||
{
|
||||
SetDstGpr(Result);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Move result and use as Method Address.
|
||||
case AssignmentOperation.MoveAndSetMaddr:
|
||||
{
|
||||
SetDstGpr(Result);
|
||||
|
||||
SetMethAddr(Result);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Fetch parameter and send result.
|
||||
case AssignmentOperation.FetchAndSend:
|
||||
{
|
||||
SetDstGpr(FetchParam());
|
||||
|
||||
Send(Vmm, Result);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Move and send result.
|
||||
case AssignmentOperation.MoveAndSend:
|
||||
{
|
||||
SetDstGpr(Result);
|
||||
|
||||
Send(Vmm, Result);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Fetch parameter and use result as Method Address.
|
||||
case AssignmentOperation.FetchAndSetMaddr:
|
||||
{
|
||||
SetDstGpr(FetchParam());
|
||||
|
||||
SetMethAddr(Result);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Move result and use as Method Address, then fetch and send paramter.
|
||||
case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend:
|
||||
{
|
||||
SetDstGpr(Result);
|
||||
|
||||
SetMethAddr(Result);
|
||||
|
||||
Send(Vmm, FetchParam());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Move result and use as Method Address, then send bits 17:12 of result.
|
||||
case AssignmentOperation.MoveAndSetMaddrThenSendHigh:
|
||||
{
|
||||
SetDstGpr(Result);
|
||||
|
||||
SetMethAddr(Result);
|
||||
|
||||
Send(Vmm, (Result >> 12) & 0x3f);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Branch.
|
||||
bool OnNotZero = ((OpCode >> 4) & 1) != 0;
|
||||
|
||||
bool Taken = OnNotZero
|
||||
? GetGprA() != 0
|
||||
: GetGprA() == 0;
|
||||
|
||||
if (Taken)
|
||||
{
|
||||
Pc = BaseAddr + (GetImm() << 2);
|
||||
|
||||
bool NoDelays = (OpCode & 0x20) != 0;
|
||||
|
||||
if (NoDelays)
|
||||
{
|
||||
FetchOpCode(Vmm);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Exit = (OpCode & 0x80) != 0;
|
||||
|
||||
return !Exit;
|
||||
}
|
||||
|
||||
private void FetchOpCode(NvGpuVmm Vmm)
|
||||
{
|
||||
OpCode = PipeOp;
|
||||
|
||||
PipeOp = Vmm.ReadInt32(Pc);
|
||||
|
||||
Pc += 4;
|
||||
}
|
||||
|
||||
private int GetAluResult()
|
||||
{
|
||||
AluOperation Op = (AluOperation)(OpCode & 7);
|
||||
|
||||
switch (Op)
|
||||
{
|
||||
case AluOperation.AluReg:
|
||||
{
|
||||
AluRegOperation AluOp = (AluRegOperation)((OpCode >> 17) & 0x1f);
|
||||
|
||||
return GetAluResult(AluOp, GetGprA(), GetGprB());
|
||||
}
|
||||
|
||||
case AluOperation.AddImmediate:
|
||||
{
|
||||
return GetGprA() + GetImm();
|
||||
}
|
||||
|
||||
case AluOperation.BitfieldReplace:
|
||||
case AluOperation.BitfieldExtractLslImm:
|
||||
case AluOperation.BitfieldExtractLslReg:
|
||||
{
|
||||
int BfSrcBit = (OpCode >> 17) & 0x1f;
|
||||
int BfSize = (OpCode >> 22) & 0x1f;
|
||||
int BfDstBit = (OpCode >> 27) & 0x1f;
|
||||
|
||||
int BfMask = (1 << BfSize) - 1;
|
||||
|
||||
int Dst = GetGprA();
|
||||
int Src = GetGprB();
|
||||
|
||||
switch (Op)
|
||||
{
|
||||
case AluOperation.BitfieldReplace:
|
||||
{
|
||||
Src = (int)((uint)Src >> BfSrcBit) & BfMask;
|
||||
|
||||
Dst &= ~(BfMask << BfDstBit);
|
||||
|
||||
Dst |= Src << BfDstBit;
|
||||
|
||||
return Dst;
|
||||
}
|
||||
|
||||
case AluOperation.BitfieldExtractLslImm:
|
||||
{
|
||||
Src = (int)((uint)Src >> Dst) & BfMask;
|
||||
|
||||
return Src << BfDstBit;
|
||||
}
|
||||
|
||||
case AluOperation.BitfieldExtractLslReg:
|
||||
{
|
||||
Src = (int)((uint)Src >> BfSrcBit) & BfMask;
|
||||
|
||||
return Src << Dst;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case AluOperation.ReadImmediate:
|
||||
{
|
||||
return Read(GetGprA() + GetImm());
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException(nameof(OpCode));
|
||||
}
|
||||
|
||||
private int GetAluResult(AluRegOperation AluOp, int A, int B)
|
||||
{
|
||||
switch (AluOp)
|
||||
{
|
||||
case AluRegOperation.Add:
|
||||
{
|
||||
ulong Result = (ulong)A + (ulong)B;
|
||||
|
||||
Carry = Result > 0xffffffff;
|
||||
|
||||
return (int)Result;
|
||||
}
|
||||
|
||||
case AluRegOperation.AddWithCarry:
|
||||
{
|
||||
ulong Result = (ulong)A + (ulong)B + (Carry ? 1UL : 0UL);
|
||||
|
||||
Carry = Result > 0xffffffff;
|
||||
|
||||
return (int)Result;
|
||||
}
|
||||
|
||||
case AluRegOperation.Subtract:
|
||||
{
|
||||
ulong Result = (ulong)A - (ulong)B;
|
||||
|
||||
Carry = Result < 0x100000000;
|
||||
|
||||
return (int)Result;
|
||||
}
|
||||
|
||||
case AluRegOperation.SubtractWithBorrow:
|
||||
{
|
||||
ulong Result = (ulong)A - (ulong)B - (Carry ? 0UL : 1UL);
|
||||
|
||||
Carry = Result < 0x100000000;
|
||||
|
||||
return (int)Result;
|
||||
}
|
||||
|
||||
case AluRegOperation.BitwiseExclusiveOr: return A ^ B;
|
||||
case AluRegOperation.BitwiseOr: return A | B;
|
||||
case AluRegOperation.BitwiseAnd: return A & B;
|
||||
case AluRegOperation.BitwiseAndNot: return A & ~B;
|
||||
case AluRegOperation.BitwiseNotAnd: return ~(A & B);
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(AluOp));
|
||||
}
|
||||
|
||||
private int GetImm()
|
||||
{
|
||||
//Note: The immediate is signed, the sign-extension is intended here.
|
||||
return OpCode >> 14;
|
||||
}
|
||||
|
||||
private void SetMethAddr(int Value)
|
||||
{
|
||||
MethAddr = (Value >> 0) & 0xfff;
|
||||
MethIncr = (Value >> 12) & 0x3f;
|
||||
}
|
||||
|
||||
private void SetDstGpr(int Value)
|
||||
{
|
||||
Gprs[(OpCode >> 8) & 7] = Value;
|
||||
}
|
||||
|
||||
private int GetGprA()
|
||||
{
|
||||
return GetGprValue((OpCode >> 11) & 7);
|
||||
}
|
||||
|
||||
private int GetGprB()
|
||||
{
|
||||
return GetGprValue((OpCode >> 14) & 7);
|
||||
}
|
||||
|
||||
private int GetGprValue(int Index)
|
||||
{
|
||||
return Index != 0 ? Gprs[Index] : 0;
|
||||
}
|
||||
|
||||
private int FetchParam()
|
||||
{
|
||||
int Value;
|
||||
|
||||
//If we don't have any parameters in the FIFO,
|
||||
//keep running the PFIFO engine until it writes the parameters.
|
||||
while (!Fifo.TryDequeue(out Value))
|
||||
{
|
||||
if (!PFifo.Step())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return Value;
|
||||
}
|
||||
|
||||
private int Read(int Reg)
|
||||
{
|
||||
return Engine.Registers[Reg];
|
||||
}
|
||||
|
||||
private void Send(NvGpuVmm Vmm, int Value)
|
||||
{
|
||||
NvGpuPBEntry PBEntry = new NvGpuPBEntry(MethAddr, 0, Value);
|
||||
|
||||
Engine.CallMethod(Vmm, PBEntry);
|
||||
|
||||
MethAddr += MethIncr;
|
||||
}
|
||||
}
|
||||
}
|
45
Ryujinx.HLE/Gpu/NvGpu.cs
Normal file
45
Ryujinx.HLE/Gpu/NvGpu.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using Ryujinx.Graphics.Gal;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class NvGpu
|
||||
{
|
||||
public IGalRenderer Renderer { get; private set; }
|
||||
|
||||
public NvGpuFifo Fifo { get; private set; }
|
||||
|
||||
public NvGpuEngine2d Engine2d { get; private set; }
|
||||
public NvGpuEngine3d Engine3d { get; private set; }
|
||||
|
||||
private Thread FifoProcessing;
|
||||
|
||||
private bool KeepRunning;
|
||||
|
||||
public NvGpu(IGalRenderer Renderer)
|
||||
{
|
||||
this.Renderer = Renderer;
|
||||
|
||||
Fifo = new NvGpuFifo(this);
|
||||
|
||||
Engine2d = new NvGpuEngine2d(this);
|
||||
Engine3d = new NvGpuEngine3d(this);
|
||||
|
||||
KeepRunning = true;
|
||||
|
||||
FifoProcessing = new Thread(ProcessFifo);
|
||||
|
||||
FifoProcessing.Start();
|
||||
}
|
||||
|
||||
private void ProcessFifo()
|
||||
{
|
||||
while (KeepRunning)
|
||||
{
|
||||
Fifo.DispatchCalls();
|
||||
|
||||
Thread.Yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/Gpu/NvGpuBufferType.cs
Normal file
9
Ryujinx.HLE/Gpu/NvGpuBufferType.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
enum NvGpuBufferType
|
||||
{
|
||||
Index,
|
||||
Vertex,
|
||||
Texture
|
||||
}
|
||||
}
|
11
Ryujinx.HLE/Gpu/NvGpuEngine.cs
Normal file
11
Ryujinx.HLE/Gpu/NvGpuEngine.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
enum NvGpuEngine
|
||||
{
|
||||
_2d = 0x902d,
|
||||
_3d = 0xb197,
|
||||
Compute = 0xb1c0,
|
||||
Kepler = 0xa140,
|
||||
Dma = 0xb0b5
|
||||
}
|
||||
}
|
168
Ryujinx.HLE/Gpu/NvGpuEngine2d.cs
Normal file
168
Ryujinx.HLE/Gpu/NvGpuEngine2d.cs
Normal file
|
@ -0,0 +1,168 @@
|
|||
using Ryujinx.Graphics.Gal;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class NvGpuEngine2d : INvGpuEngine
|
||||
{
|
||||
private enum CopyOperation
|
||||
{
|
||||
SrcCopyAnd,
|
||||
RopAnd,
|
||||
Blend,
|
||||
SrcCopy,
|
||||
Rop,
|
||||
SrcCopyPremult,
|
||||
BlendPremult
|
||||
}
|
||||
|
||||
public int[] Registers { get; private set; }
|
||||
|
||||
private NvGpu Gpu;
|
||||
|
||||
private Dictionary<int, NvGpuMethod> Methods;
|
||||
|
||||
public NvGpuEngine2d(NvGpu Gpu)
|
||||
{
|
||||
this.Gpu = Gpu;
|
||||
|
||||
Registers = new int[0xe00];
|
||||
|
||||
Methods = new Dictionary<int, NvGpuMethod>();
|
||||
|
||||
void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method)
|
||||
{
|
||||
while (Count-- > 0)
|
||||
{
|
||||
Methods.Add(Meth, Method);
|
||||
|
||||
Meth += Stride;
|
||||
}
|
||||
}
|
||||
|
||||
AddMethod(0xb5, 1, 1, TextureCopy);
|
||||
}
|
||||
|
||||
public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
|
||||
{
|
||||
Method(Vmm, PBEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRegister(PBEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private void TextureCopy(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
CopyOperation Operation = (CopyOperation)ReadRegister(NvGpuEngine2dReg.CopyOperation);
|
||||
|
||||
bool SrcLinear = ReadRegister(NvGpuEngine2dReg.SrcLinear) != 0;
|
||||
int SrcWidth = ReadRegister(NvGpuEngine2dReg.SrcWidth);
|
||||
int SrcHeight = ReadRegister(NvGpuEngine2dReg.SrcHeight);
|
||||
|
||||
bool DstLinear = ReadRegister(NvGpuEngine2dReg.DstLinear) != 0;
|
||||
int DstWidth = ReadRegister(NvGpuEngine2dReg.DstWidth);
|
||||
int DstHeight = ReadRegister(NvGpuEngine2dReg.DstHeight);
|
||||
int DstPitch = ReadRegister(NvGpuEngine2dReg.DstPitch);
|
||||
int DstBlkDim = ReadRegister(NvGpuEngine2dReg.DstBlockDimensions);
|
||||
|
||||
TextureSwizzle DstSwizzle = DstLinear
|
||||
? TextureSwizzle.Pitch
|
||||
: TextureSwizzle.BlockLinear;
|
||||
|
||||
int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf);
|
||||
|
||||
long Tag = Vmm.GetPhysicalAddress(MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress));
|
||||
|
||||
long SrcAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress);
|
||||
long DstAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.DstAddress);
|
||||
|
||||
bool IsFbTexture = Gpu.Engine3d.IsFrameBufferPosition(Tag);
|
||||
|
||||
if (IsFbTexture && DstLinear)
|
||||
{
|
||||
DstSwizzle = TextureSwizzle.BlockLinear;
|
||||
}
|
||||
|
||||
Texture DstTexture = new Texture(
|
||||
DstAddress,
|
||||
DstWidth,
|
||||
DstHeight,
|
||||
DstBlockHeight,
|
||||
DstBlockHeight,
|
||||
DstSwizzle,
|
||||
GalTextureFormat.A8B8G8R8);
|
||||
|
||||
if (IsFbTexture)
|
||||
{
|
||||
//TODO: Change this when the correct frame buffer resolution is used.
|
||||
//Currently, the frame buffer size is hardcoded to 1280x720.
|
||||
SrcWidth = 1280;
|
||||
SrcHeight = 720;
|
||||
|
||||
Gpu.Renderer.GetFrameBufferData(Tag, (byte[] Buffer) =>
|
||||
{
|
||||
CopyTexture(
|
||||
Vmm,
|
||||
DstTexture,
|
||||
Buffer,
|
||||
SrcWidth,
|
||||
SrcHeight);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
long Size = SrcWidth * SrcHeight * 4;
|
||||
|
||||
byte[] Buffer = Vmm.ReadBytes(SrcAddress, Size);
|
||||
|
||||
CopyTexture(
|
||||
Vmm,
|
||||
DstTexture,
|
||||
Buffer,
|
||||
SrcWidth,
|
||||
SrcHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyTexture(
|
||||
NvGpuVmm Vmm,
|
||||
Texture Texture,
|
||||
byte[] Buffer,
|
||||
int Width,
|
||||
int Height)
|
||||
{
|
||||
TextureWriter.Write(Vmm, Texture, Buffer, Width, Height);
|
||||
}
|
||||
|
||||
private long MakeInt64From2xInt32(NvGpuEngine2dReg Reg)
|
||||
{
|
||||
return
|
||||
(long)Registers[(int)Reg + 0] << 32 |
|
||||
(uint)Registers[(int)Reg + 1];
|
||||
}
|
||||
|
||||
private void WriteRegister(NvGpuPBEntry PBEntry)
|
||||
{
|
||||
int ArgsCount = PBEntry.Arguments.Count;
|
||||
|
||||
if (ArgsCount > 0)
|
||||
{
|
||||
Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private int ReadRegister(NvGpuEngine2dReg Reg)
|
||||
{
|
||||
return Registers[(int)Reg];
|
||||
}
|
||||
|
||||
private void WriteRegister(NvGpuEngine2dReg Reg, int Value)
|
||||
{
|
||||
Registers[(int)Reg] = Value;
|
||||
}
|
||||
}
|
||||
}
|
25
Ryujinx.HLE/Gpu/NvGpuEngine2dReg.cs
Normal file
25
Ryujinx.HLE/Gpu/NvGpuEngine2dReg.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
enum NvGpuEngine2dReg
|
||||
{
|
||||
DstFormat = 0x80,
|
||||
DstLinear = 0x81,
|
||||
DstBlockDimensions = 0x82,
|
||||
DstDepth = 0x83,
|
||||
DstLayer = 0x84,
|
||||
DstPitch = 0x85,
|
||||
DstWidth = 0x86,
|
||||
DstHeight = 0x87,
|
||||
DstAddress = 0x88,
|
||||
SrcFormat = 0x8c,
|
||||
SrcLinear = 0x8d,
|
||||
SrcBlockDimensions = 0x8e,
|
||||
SrcDepth = 0x8f,
|
||||
SrcLayer = 0x90,
|
||||
SrcPitch = 0x91,
|
||||
SrcWidth = 0x92,
|
||||
SrcHeight = 0x93,
|
||||
SrcAddress = 0x94,
|
||||
CopyOperation = 0xab
|
||||
}
|
||||
}
|
531
Ryujinx.HLE/Gpu/NvGpuEngine3d.cs
Normal file
531
Ryujinx.HLE/Gpu/NvGpuEngine3d.cs
Normal file
|
@ -0,0 +1,531 @@
|
|||
using Ryujinx.Graphics.Gal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class NvGpuEngine3d : INvGpuEngine
|
||||
{
|
||||
public int[] Registers { get; private set; }
|
||||
|
||||
private NvGpu Gpu;
|
||||
|
||||
private Dictionary<int, NvGpuMethod> Methods;
|
||||
|
||||
private struct ConstBuffer
|
||||
{
|
||||
public bool Enabled;
|
||||
public long Position;
|
||||
public int Size;
|
||||
}
|
||||
|
||||
private ConstBuffer[][] ConstBuffers;
|
||||
|
||||
private HashSet<long> FrameBuffers;
|
||||
|
||||
public NvGpuEngine3d(NvGpu Gpu)
|
||||
{
|
||||
this.Gpu = Gpu;
|
||||
|
||||
Registers = new int[0xe00];
|
||||
|
||||
Methods = new Dictionary<int, NvGpuMethod>();
|
||||
|
||||
void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method)
|
||||
{
|
||||
while (Count-- > 0)
|
||||
{
|
||||
Methods.Add(Meth, Method);
|
||||
|
||||
Meth += Stride;
|
||||
}
|
||||
}
|
||||
|
||||
AddMethod(0x585, 1, 1, VertexEndGl);
|
||||
AddMethod(0x674, 1, 1, ClearBuffers);
|
||||
AddMethod(0x6c3, 1, 1, QueryControl);
|
||||
AddMethod(0x8e4, 16, 1, CbData);
|
||||
AddMethod(0x904, 5, 8, CbBind);
|
||||
|
||||
ConstBuffers = new ConstBuffer[6][];
|
||||
|
||||
for (int Index = 0; Index < ConstBuffers.Length; Index++)
|
||||
{
|
||||
ConstBuffers[Index] = new ConstBuffer[18];
|
||||
}
|
||||
|
||||
FrameBuffers = new HashSet<long>();
|
||||
}
|
||||
|
||||
public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
|
||||
{
|
||||
Method(Vmm, PBEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRegister(PBEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private void VertexEndGl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
SetFrameBuffer(Vmm, 0);
|
||||
|
||||
long[] Tags = UploadShaders(Vmm);
|
||||
|
||||
Gpu.Renderer.BindProgram();
|
||||
|
||||
SetAlphaBlending();
|
||||
|
||||
UploadTextures(Vmm, Tags);
|
||||
UploadUniforms(Vmm);
|
||||
UploadVertexArrays(Vmm);
|
||||
}
|
||||
|
||||
private void ClearBuffers(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
int Arg0 = PBEntry.Arguments[0];
|
||||
|
||||
int FbIndex = (Arg0 >> 6) & 0xf;
|
||||
|
||||
int Layer = (Arg0 >> 10) & 0x3ff;
|
||||
|
||||
GalClearBufferFlags Flags = (GalClearBufferFlags)(Arg0 & 0x3f);
|
||||
|
||||
SetFrameBuffer(Vmm, 0);
|
||||
|
||||
//TODO: Enable this once the frame buffer problems are fixed.
|
||||
//Gpu.Renderer.ClearBuffers(Layer, Flags);
|
||||
}
|
||||
|
||||
private void SetFrameBuffer(NvGpuVmm Vmm, int FbIndex)
|
||||
{
|
||||
long VA = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + FbIndex * 0x10);
|
||||
|
||||
long PA = Vmm.GetPhysicalAddress(VA);
|
||||
|
||||
FrameBuffers.Add(PA);
|
||||
|
||||
int Width = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth + FbIndex * 0x10);
|
||||
int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10);
|
||||
|
||||
//Note: Using the Width/Height results seems to give incorrect results.
|
||||
//Maybe the size of all frame buffers is hardcoded to screen size? This seems unlikely.
|
||||
Gpu.Renderer.CreateFrameBuffer(PA, 1280, 720);
|
||||
Gpu.Renderer.BindFrameBuffer(PA);
|
||||
}
|
||||
|
||||
private long[] UploadShaders(NvGpuVmm Vmm)
|
||||
{
|
||||
long[] Tags = new long[5];
|
||||
|
||||
long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
|
||||
|
||||
for (int Index = 0; Index < 6; Index++)
|
||||
{
|
||||
int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + Index * 0x10);
|
||||
int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + Index * 0x10);
|
||||
|
||||
//Note: Vertex Program (B) is always enabled.
|
||||
bool Enable = (Control & 1) != 0 || Index == 1;
|
||||
|
||||
if (!Enable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
long Tag = BasePosition + (uint)Offset;
|
||||
|
||||
GalShaderType ShaderType = GetTypeFromProgram(Index);
|
||||
|
||||
Tags[(int)ShaderType] = Tag;
|
||||
|
||||
Gpu.Renderer.CreateShader(Vmm, Tag, ShaderType);
|
||||
Gpu.Renderer.BindShader(Tag);
|
||||
}
|
||||
|
||||
int RawSX = ReadRegister(NvGpuEngine3dReg.ViewportScaleX);
|
||||
int RawSY = ReadRegister(NvGpuEngine3dReg.ViewportScaleY);
|
||||
|
||||
float SX = BitConverter.Int32BitsToSingle(RawSX);
|
||||
float SY = BitConverter.Int32BitsToSingle(RawSY);
|
||||
|
||||
float SignX = MathF.Sign(SX);
|
||||
float SignY = MathF.Sign(SY);
|
||||
|
||||
Gpu.Renderer.SetUniform2F(GalConsts.FlipUniformName, SignX, SignY);
|
||||
|
||||
return Tags;
|
||||
}
|
||||
|
||||
private static GalShaderType GetTypeFromProgram(int Program)
|
||||
{
|
||||
switch (Program)
|
||||
{
|
||||
case 0:
|
||||
case 1: return GalShaderType.Vertex;
|
||||
case 2: return GalShaderType.TessControl;
|
||||
case 3: return GalShaderType.TessEvaluation;
|
||||
case 4: return GalShaderType.Geometry;
|
||||
case 5: return GalShaderType.Fragment;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(Program));
|
||||
}
|
||||
|
||||
private void SetAlphaBlending()
|
||||
{
|
||||
//TODO: Support independent blend properly.
|
||||
bool Enable = (ReadRegister(NvGpuEngine3dReg.IBlendNEnable) & 1) != 0;
|
||||
|
||||
Gpu.Renderer.SetBlendEnable(Enable);
|
||||
|
||||
if (!Enable)
|
||||
{
|
||||
//If blend is not enabled, then the other values have no effect.
|
||||
//Note that if it is disabled, the register may contain invalid values.
|
||||
return;
|
||||
}
|
||||
|
||||
bool BlendSeparateAlpha = (ReadRegister(NvGpuEngine3dReg.IBlendNSeparateAlpha) & 1) != 0;
|
||||
|
||||
GalBlendEquation EquationRgb = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.IBlendNEquationRgb);
|
||||
|
||||
GalBlendFactor FuncSrcRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncSrcRgb);
|
||||
GalBlendFactor FuncDstRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncDstRgb);
|
||||
|
||||
if (BlendSeparateAlpha)
|
||||
{
|
||||
GalBlendEquation EquationAlpha = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.IBlendNEquationAlpha);
|
||||
|
||||
GalBlendFactor FuncSrcAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncSrcAlpha);
|
||||
GalBlendFactor FuncDstAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.IBlendNFuncDstAlpha);
|
||||
|
||||
Gpu.Renderer.SetBlendSeparate(
|
||||
EquationRgb,
|
||||
EquationAlpha,
|
||||
FuncSrcRgb,
|
||||
FuncDstRgb,
|
||||
FuncSrcAlpha,
|
||||
FuncDstAlpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
Gpu.Renderer.SetBlend(EquationRgb, FuncSrcRgb, FuncDstRgb);
|
||||
}
|
||||
}
|
||||
|
||||
private void UploadTextures(NvGpuVmm Vmm, long[] Tags)
|
||||
{
|
||||
long BaseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
|
||||
|
||||
int TextureCbIndex = ReadRegister(NvGpuEngine3dReg.TextureCbIndex);
|
||||
|
||||
//Note: On the emulator renderer, Texture Unit 0 is
|
||||
//reserved for drawing the frame buffer.
|
||||
int TexIndex = 1;
|
||||
|
||||
for (int Index = 0; Index < Tags.Length; Index++)
|
||||
{
|
||||
foreach (ShaderDeclInfo DeclInfo in Gpu.Renderer.GetTextureUsage(Tags[Index]))
|
||||
{
|
||||
long Position = ConstBuffers[Index][TextureCbIndex].Position;
|
||||
|
||||
UploadTexture(Vmm, Position, TexIndex, DeclInfo.Index);
|
||||
|
||||
Gpu.Renderer.SetUniform1(DeclInfo.Name, TexIndex);
|
||||
|
||||
TexIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UploadTexture(NvGpuVmm Vmm, long BasePosition, int TexIndex, int HndIndex)
|
||||
{
|
||||
long Position = BasePosition + HndIndex * 4;
|
||||
|
||||
int TextureHandle = Vmm.ReadInt32(Position);
|
||||
|
||||
int TicIndex = (TextureHandle >> 0) & 0xfffff;
|
||||
int TscIndex = (TextureHandle >> 20) & 0xfff;
|
||||
|
||||
long TicPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexHeaderPoolOffset);
|
||||
long TscPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexSamplerPoolOffset);
|
||||
|
||||
TicPosition += TicIndex * 0x20;
|
||||
TscPosition += TscIndex * 0x20;
|
||||
|
||||
GalTextureSampler Sampler = TextureFactory.MakeSampler(Gpu, Vmm, TscPosition);
|
||||
|
||||
long TextureAddress = Vmm.ReadInt64(TicPosition + 4) & 0xffffffffffff;
|
||||
|
||||
long Tag = TextureAddress;
|
||||
|
||||
TextureAddress = Vmm.GetPhysicalAddress(TextureAddress);
|
||||
|
||||
if (IsFrameBufferPosition(TextureAddress))
|
||||
{
|
||||
//This texture is a frame buffer texture,
|
||||
//we shouldn't read anything from memory and bind
|
||||
//the frame buffer texture instead, since we're not
|
||||
//really writing anything to memory.
|
||||
Gpu.Renderer.BindFrameBufferTexture(TextureAddress, TexIndex, Sampler);
|
||||
}
|
||||
else
|
||||
{
|
||||
GalTexture NewTexture = TextureFactory.MakeTexture(Vmm, TicPosition);
|
||||
|
||||
long Size = (uint)TextureHelper.GetTextureSize(NewTexture);
|
||||
|
||||
if (Gpu.Renderer.TryGetCachedTexture(Tag, Size, out GalTexture Texture))
|
||||
{
|
||||
if (NewTexture.Equals(Texture) && !Vmm.IsRegionModified(Tag, Size, NvGpuBufferType.Texture))
|
||||
{
|
||||
Gpu.Renderer.BindTexture(Tag, TexIndex);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] Data = TextureFactory.GetTextureData(Vmm, TicPosition);
|
||||
|
||||
Gpu.Renderer.SetTextureAndSampler(Tag, Data, NewTexture, Sampler);
|
||||
|
||||
Gpu.Renderer.BindTexture(Tag, TexIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void UploadUniforms(NvGpuVmm Vmm)
|
||||
{
|
||||
long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
|
||||
|
||||
for (int Index = 0; Index < 5; Index++)
|
||||
{
|
||||
int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + (Index + 1) * 0x10);
|
||||
int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + (Index + 1) * 0x10);
|
||||
|
||||
//Note: Vertex Program (B) is always enabled.
|
||||
bool Enable = (Control & 1) != 0 || Index == 0;
|
||||
|
||||
if (!Enable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int Cbuf = 0; Cbuf < ConstBuffers.Length; Cbuf++)
|
||||
{
|
||||
ConstBuffer Cb = ConstBuffers[Index][Cbuf];
|
||||
|
||||
if (Cb.Enabled)
|
||||
{
|
||||
byte[] Data = Vmm.ReadBytes(Cb.Position, (uint)Cb.Size);
|
||||
|
||||
Gpu.Renderer.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UploadVertexArrays(NvGpuVmm Vmm)
|
||||
{
|
||||
long IndexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress);
|
||||
|
||||
int IndexSize = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat);
|
||||
int IndexFirst = ReadRegister(NvGpuEngine3dReg.IndexBatchFirst);
|
||||
int IndexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount);
|
||||
|
||||
GalIndexFormat IndexFormat = (GalIndexFormat)IndexSize;
|
||||
|
||||
IndexSize = 1 << IndexSize;
|
||||
|
||||
if (IndexSize > 4)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (IndexSize != 0)
|
||||
{
|
||||
int IbSize = IndexCount * IndexSize;
|
||||
|
||||
bool IboCached = Gpu.Renderer.IsIboCached(IndexPosition, (uint)IbSize);
|
||||
|
||||
if (!IboCached || Vmm.IsRegionModified(IndexPosition, (uint)IbSize, NvGpuBufferType.Index))
|
||||
{
|
||||
byte[] Data = Vmm.ReadBytes(IndexPosition, (uint)IbSize);
|
||||
|
||||
Gpu.Renderer.CreateIbo(IndexPosition, Data);
|
||||
}
|
||||
|
||||
Gpu.Renderer.SetIndexArray(IndexPosition, IbSize, IndexFormat);
|
||||
}
|
||||
|
||||
List<GalVertexAttrib>[] Attribs = new List<GalVertexAttrib>[32];
|
||||
|
||||
for (int Attr = 0; Attr < 16; Attr++)
|
||||
{
|
||||
int Packed = ReadRegister(NvGpuEngine3dReg.VertexAttribNFormat + Attr);
|
||||
|
||||
int ArrayIndex = Packed & 0x1f;
|
||||
|
||||
if (Attribs[ArrayIndex] == null)
|
||||
{
|
||||
Attribs[ArrayIndex] = new List<GalVertexAttrib>();
|
||||
}
|
||||
|
||||
Attribs[ArrayIndex].Add(new GalVertexAttrib(
|
||||
Attr,
|
||||
((Packed >> 6) & 0x1) != 0,
|
||||
(Packed >> 7) & 0x3fff,
|
||||
(GalVertexAttribSize)((Packed >> 21) & 0x3f),
|
||||
(GalVertexAttribType)((Packed >> 27) & 0x7),
|
||||
((Packed >> 31) & 0x1) != 0));
|
||||
}
|
||||
|
||||
int VertexFirst = ReadRegister(NvGpuEngine3dReg.VertexArrayFirst);
|
||||
int VertexCount = ReadRegister(NvGpuEngine3dReg.VertexArrayCount);
|
||||
|
||||
int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl);
|
||||
|
||||
for (int Index = 0; Index < 32; Index++)
|
||||
{
|
||||
if (Attribs[Index] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int Control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + Index * 4);
|
||||
|
||||
bool Enable = (Control & 0x1000) != 0;
|
||||
|
||||
long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + Index * 4);
|
||||
long VertexEndPos = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + Index * 2);
|
||||
|
||||
if (!Enable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int Stride = Control & 0xfff;
|
||||
|
||||
long VbSize = 0;
|
||||
|
||||
if (IndexCount != 0)
|
||||
{
|
||||
VbSize = (VertexEndPos - VertexPosition) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
VbSize = VertexCount * Stride;
|
||||
}
|
||||
|
||||
bool VboCached = Gpu.Renderer.IsVboCached(VertexPosition, VbSize);
|
||||
|
||||
if (!VboCached || Vmm.IsRegionModified(VertexPosition, VbSize, NvGpuBufferType.Vertex))
|
||||
{
|
||||
byte[] Data = Vmm.ReadBytes(VertexPosition, VbSize);
|
||||
|
||||
Gpu.Renderer.CreateVbo(VertexPosition, Data);
|
||||
}
|
||||
|
||||
Gpu.Renderer.SetVertexArray(Index, Stride, VertexPosition, Attribs[Index].ToArray());
|
||||
}
|
||||
|
||||
GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff);
|
||||
|
||||
if (IndexCount != 0)
|
||||
{
|
||||
Gpu.Renderer.DrawElements(IndexPosition, IndexFirst, PrimType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Gpu.Renderer.DrawArrays(VertexFirst, VertexCount, PrimType);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryControl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.QueryAddress);
|
||||
|
||||
int Seq = Registers[(int)NvGpuEngine3dReg.QuerySequence];
|
||||
int Ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl];
|
||||
|
||||
int Mode = Ctrl & 3;
|
||||
|
||||
if (Mode == 0)
|
||||
{
|
||||
//Write mode.
|
||||
Vmm.WriteInt32(Position, Seq);
|
||||
}
|
||||
|
||||
WriteRegister(PBEntry);
|
||||
}
|
||||
|
||||
private void CbData(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress);
|
||||
|
||||
int Offset = ReadRegister(NvGpuEngine3dReg.ConstBufferOffset);
|
||||
|
||||
foreach (int Arg in PBEntry.Arguments)
|
||||
{
|
||||
Vmm.WriteInt32(Position + Offset, Arg);
|
||||
|
||||
Offset += 4;
|
||||
}
|
||||
|
||||
WriteRegister(NvGpuEngine3dReg.ConstBufferOffset, Offset);
|
||||
}
|
||||
|
||||
private void CbBind(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
int Stage = (PBEntry.Method - 0x904) >> 3;
|
||||
|
||||
int Index = PBEntry.Arguments[0];
|
||||
|
||||
bool Enabled = (Index & 1) != 0;
|
||||
|
||||
Index = (Index >> 4) & 0x1f;
|
||||
|
||||
long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress);
|
||||
|
||||
ConstBuffers[Stage][Index].Position = Position;
|
||||
ConstBuffers[Stage][Index].Enabled = Enabled;
|
||||
|
||||
ConstBuffers[Stage][Index].Size = ReadRegister(NvGpuEngine3dReg.ConstBufferSize);
|
||||
}
|
||||
|
||||
private long MakeInt64From2xInt32(NvGpuEngine3dReg Reg)
|
||||
{
|
||||
return
|
||||
(long)Registers[(int)Reg + 0] << 32 |
|
||||
(uint)Registers[(int)Reg + 1];
|
||||
}
|
||||
|
||||
private void WriteRegister(NvGpuPBEntry PBEntry)
|
||||
{
|
||||
int ArgsCount = PBEntry.Arguments.Count;
|
||||
|
||||
if (ArgsCount > 0)
|
||||
{
|
||||
Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private int ReadRegister(NvGpuEngine3dReg Reg)
|
||||
{
|
||||
return Registers[(int)Reg];
|
||||
}
|
||||
|
||||
private void WriteRegister(NvGpuEngine3dReg Reg, int Value)
|
||||
{
|
||||
Registers[(int)Reg] = Value;
|
||||
}
|
||||
|
||||
public bool IsFrameBufferPosition(long Position)
|
||||
{
|
||||
return FrameBuffers.Contains(Position);
|
||||
}
|
||||
}
|
||||
}
|
61
Ryujinx.HLE/Gpu/NvGpuEngine3dReg.cs
Normal file
61
Ryujinx.HLE/Gpu/NvGpuEngine3dReg.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
enum NvGpuEngine3dReg
|
||||
{
|
||||
FrameBufferNAddress = 0x200,
|
||||
FrameBufferNWidth = 0x202,
|
||||
FrameBufferNHeight = 0x203,
|
||||
FrameBufferNFormat = 0x204,
|
||||
ViewportScaleX = 0x280,
|
||||
ViewportScaleY = 0x281,
|
||||
ViewportScaleZ = 0x282,
|
||||
ViewportTranslateX = 0x283,
|
||||
ViewportTranslateY = 0x284,
|
||||
ViewportTranslateZ = 0x285,
|
||||
VertexArrayFirst = 0x35d,
|
||||
VertexArrayCount = 0x35e,
|
||||
VertexAttribNFormat = 0x458,
|
||||
IBlendEnable = 0x4b9,
|
||||
BlendSeparateAlpha = 0x4cf,
|
||||
BlendEquationRgb = 0x4d0,
|
||||
BlendFuncSrcRgb = 0x4d1,
|
||||
BlendFuncDstRgb = 0x4d2,
|
||||
BlendEquationAlpha = 0x4d3,
|
||||
BlendFuncSrcAlpha = 0x4d4,
|
||||
BlendFuncDstAlpha = 0x4d6,
|
||||
BlendEnableMaster = 0x4d7,
|
||||
IBlendNEnable = 0x4d8,
|
||||
VertexArrayElemBase = 0x50d,
|
||||
TexHeaderPoolOffset = 0x55d,
|
||||
TexSamplerPoolOffset = 0x557,
|
||||
ShaderAddress = 0x582,
|
||||
VertexBeginGl = 0x586,
|
||||
IndexArrayAddress = 0x5f2,
|
||||
IndexArrayEndAddr = 0x5f4,
|
||||
IndexArrayFormat = 0x5f6,
|
||||
IndexBatchFirst = 0x5f7,
|
||||
IndexBatchCount = 0x5f8,
|
||||
QueryAddress = 0x6c0,
|
||||
QuerySequence = 0x6c2,
|
||||
QueryControl = 0x6c3,
|
||||
VertexArrayNControl = 0x700,
|
||||
VertexArrayNAddress = 0x701,
|
||||
VertexArrayNDivisor = 0x703,
|
||||
IBlendNSeparateAlpha = 0x780,
|
||||
IBlendNEquationRgb = 0x781,
|
||||
IBlendNFuncSrcRgb = 0x782,
|
||||
IBlendNFuncDstRgb = 0x783,
|
||||
IBlendNEquationAlpha = 0x784,
|
||||
IBlendNFuncSrcAlpha = 0x785,
|
||||
IBlendNFuncDstAlpha = 0x786,
|
||||
VertexArrayNEndAddr = 0x7c0,
|
||||
ShaderNControl = 0x800,
|
||||
ShaderNOffset = 0x801,
|
||||
ShaderNMaxGprs = 0x803,
|
||||
ShaderNType = 0x804,
|
||||
ConstBufferSize = 0x8e0,
|
||||
ConstBufferAddress = 0x8e1,
|
||||
ConstBufferOffset = 0x8e3,
|
||||
TextureCbIndex = 0x982
|
||||
}
|
||||
}
|
174
Ryujinx.HLE/Gpu/NvGpuFifo.cs
Normal file
174
Ryujinx.HLE/Gpu/NvGpuFifo.cs
Normal file
|
@ -0,0 +1,174 @@
|
|||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class NvGpuFifo
|
||||
{
|
||||
private const int MacrosCount = 0x80;
|
||||
private const int MacroIndexMask = MacrosCount - 1;
|
||||
|
||||
private NvGpu Gpu;
|
||||
|
||||
private ConcurrentQueue<(NvGpuVmm, NvGpuPBEntry)> BufferQueue;
|
||||
|
||||
private NvGpuEngine[] SubChannels;
|
||||
|
||||
private struct CachedMacro
|
||||
{
|
||||
public long Position { get; private set; }
|
||||
|
||||
private MacroInterpreter Interpreter;
|
||||
|
||||
public CachedMacro(NvGpuFifo PFifo, INvGpuEngine Engine, long Position)
|
||||
{
|
||||
this.Position = Position;
|
||||
|
||||
Interpreter = new MacroInterpreter(PFifo, Engine);
|
||||
}
|
||||
|
||||
public void PushParam(int Param)
|
||||
{
|
||||
Interpreter?.Fifo.Enqueue(Param);
|
||||
}
|
||||
|
||||
public void Execute(NvGpuVmm Vmm, int Param)
|
||||
{
|
||||
Interpreter?.Execute(Vmm, Position, Param);
|
||||
}
|
||||
}
|
||||
|
||||
private long CurrMacroPosition;
|
||||
private int CurrMacroBindIndex;
|
||||
|
||||
private CachedMacro[] Macros;
|
||||
|
||||
public NvGpuFifo(NvGpu Gpu)
|
||||
{
|
||||
this.Gpu = Gpu;
|
||||
|
||||
BufferQueue = new ConcurrentQueue<(NvGpuVmm, NvGpuPBEntry)>();
|
||||
|
||||
SubChannels = new NvGpuEngine[8];
|
||||
|
||||
Macros = new CachedMacro[MacrosCount];
|
||||
}
|
||||
|
||||
public void PushBuffer(NvGpuVmm Vmm, NvGpuPBEntry[] Buffer)
|
||||
{
|
||||
foreach (NvGpuPBEntry PBEntry in Buffer)
|
||||
{
|
||||
BufferQueue.Enqueue((Vmm, PBEntry));
|
||||
}
|
||||
}
|
||||
|
||||
public void DispatchCalls()
|
||||
{
|
||||
while (Step());
|
||||
}
|
||||
|
||||
public bool Step()
|
||||
{
|
||||
if (BufferQueue.TryDequeue(out (NvGpuVmm Vmm, NvGpuPBEntry PBEntry) Tuple))
|
||||
{
|
||||
CallMethod(Tuple.Vmm, Tuple.PBEntry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
if (PBEntry.Method < 0x80)
|
||||
{
|
||||
switch ((NvGpuFifoMeth)PBEntry.Method)
|
||||
{
|
||||
case NvGpuFifoMeth.BindChannel:
|
||||
{
|
||||
NvGpuEngine Engine = (NvGpuEngine)PBEntry.Arguments[0];
|
||||
|
||||
SubChannels[PBEntry.SubChannel] = Engine;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NvGpuFifoMeth.SetMacroUploadAddress:
|
||||
{
|
||||
CurrMacroPosition = (long)((ulong)PBEntry.Arguments[0] << 2);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NvGpuFifoMeth.SendMacroCodeData:
|
||||
{
|
||||
long Position = CurrMacroPosition;
|
||||
|
||||
foreach (int Arg in PBEntry.Arguments)
|
||||
{
|
||||
Vmm.WriteInt32(Position, Arg);
|
||||
|
||||
CurrMacroPosition += 4;
|
||||
|
||||
Position += 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NvGpuFifoMeth.SetMacroBindingIndex:
|
||||
{
|
||||
CurrMacroBindIndex = PBEntry.Arguments[0];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NvGpuFifoMeth.BindMacro:
|
||||
{
|
||||
long Position = (long)((ulong)PBEntry.Arguments[0] << 2);
|
||||
|
||||
Macros[CurrMacroBindIndex] = new CachedMacro(this, Gpu.Engine3d, Position);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (SubChannels[PBEntry.SubChannel])
|
||||
{
|
||||
case NvGpuEngine._2d: Call2dMethod(Vmm, PBEntry); break;
|
||||
case NvGpuEngine._3d: Call3dMethod(Vmm, PBEntry); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Call2dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
Gpu.Engine2d.CallMethod(Vmm, PBEntry);
|
||||
}
|
||||
|
||||
private void Call3dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
|
||||
{
|
||||
if (PBEntry.Method < 0xe00)
|
||||
{
|
||||
Gpu.Engine3d.CallMethod(Vmm, PBEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
int MacroIndex = (PBEntry.Method >> 1) & MacroIndexMask;
|
||||
|
||||
if ((PBEntry.Method & 1) != 0)
|
||||
{
|
||||
foreach (int Arg in PBEntry.Arguments)
|
||||
{
|
||||
Macros[MacroIndex].PushParam(Arg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Macros[MacroIndex].Execute(Vmm, PBEntry.Arguments[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Ryujinx.HLE/Gpu/NvGpuFifoMeth.cs
Normal file
11
Ryujinx.HLE/Gpu/NvGpuFifoMeth.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
enum NvGpuFifoMeth
|
||||
{
|
||||
BindChannel = 0,
|
||||
SetMacroUploadAddress = 0x45,
|
||||
SendMacroCodeData = 0x46,
|
||||
SetMacroBindingIndex = 0x47,
|
||||
BindMacro = 0x48
|
||||
}
|
||||
}
|
4
Ryujinx.HLE/Gpu/NvGpuMethod.cs
Normal file
4
Ryujinx.HLE/Gpu/NvGpuMethod.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
delegate void NvGpuMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry);
|
||||
}
|
23
Ryujinx.HLE/Gpu/NvGpuPBEntry.cs
Normal file
23
Ryujinx.HLE/Gpu/NvGpuPBEntry.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
struct NvGpuPBEntry
|
||||
{
|
||||
public int Method { get; private set; }
|
||||
|
||||
public int SubChannel { get; private set; }
|
||||
|
||||
private int[] m_Arguments;
|
||||
|
||||
public ReadOnlyCollection<int> Arguments => Array.AsReadOnly(m_Arguments);
|
||||
|
||||
public NvGpuPBEntry(int Method, int SubChannel, params int[] Arguments)
|
||||
{
|
||||
this.Method = Method;
|
||||
this.SubChannel = SubChannel;
|
||||
this.m_Arguments = Arguments;
|
||||
}
|
||||
}
|
||||
}
|
101
Ryujinx.HLE/Gpu/NvGpuPushBuffer.cs
Normal file
101
Ryujinx.HLE/Gpu/NvGpuPushBuffer.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
static class NvGpuPushBuffer
|
||||
{
|
||||
private enum SubmissionMode
|
||||
{
|
||||
Incrementing = 1,
|
||||
NonIncrementing = 3,
|
||||
Immediate = 4,
|
||||
IncrementOnce = 5
|
||||
}
|
||||
|
||||
public static NvGpuPBEntry[] Decode(byte[] Data)
|
||||
{
|
||||
using (MemoryStream MS = new MemoryStream(Data))
|
||||
{
|
||||
BinaryReader Reader = new BinaryReader(MS);
|
||||
|
||||
List<NvGpuPBEntry> PushBuffer = new List<NvGpuPBEntry>();
|
||||
|
||||
bool CanRead() => MS.Position + 4 <= MS.Length;
|
||||
|
||||
while (CanRead())
|
||||
{
|
||||
int Packed = Reader.ReadInt32();
|
||||
|
||||
int Meth = (Packed >> 0) & 0x1fff;
|
||||
int SubC = (Packed >> 13) & 7;
|
||||
int Args = (Packed >> 16) & 0x1fff;
|
||||
int Mode = (Packed >> 29) & 7;
|
||||
|
||||
switch ((SubmissionMode)Mode)
|
||||
{
|
||||
case SubmissionMode.Incrementing:
|
||||
{
|
||||
for (int Index = 0; Index < Args && CanRead(); Index++, Meth++)
|
||||
{
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SubmissionMode.NonIncrementing:
|
||||
{
|
||||
int[] Arguments = new int[Args];
|
||||
|
||||
for (int Index = 0; Index < Arguments.Length; Index++)
|
||||
{
|
||||
if (!CanRead())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Arguments[Index] = Reader.ReadInt32();
|
||||
}
|
||||
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Arguments));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SubmissionMode.Immediate:
|
||||
{
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Args));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SubmissionMode.IncrementOnce:
|
||||
{
|
||||
if (CanRead())
|
||||
{
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
|
||||
}
|
||||
|
||||
if (CanRead() && Args > 1)
|
||||
{
|
||||
int[] Arguments = new int[Args - 1];
|
||||
|
||||
for (int Index = 0; Index < Arguments.Length && CanRead(); Index++)
|
||||
{
|
||||
Arguments[Index] = Reader.ReadInt32();
|
||||
}
|
||||
|
||||
PushBuffer.Add(new NvGpuPBEntry(Meth + 1, SubC, Arguments));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PushBuffer.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
410
Ryujinx.HLE/Gpu/NvGpuVmm.cs
Normal file
410
Ryujinx.HLE/Gpu/NvGpuVmm.cs
Normal file
|
@ -0,0 +1,410 @@
|
|||
using ChocolArm64.Memory;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class NvGpuVmm : IAMemory, IGalMemory
|
||||
{
|
||||
public const long AddrSize = 1L << 40;
|
||||
|
||||
private const int PTLvl0Bits = 14;
|
||||
private const int PTLvl1Bits = 14;
|
||||
private const int PTPageBits = 12;
|
||||
|
||||
private const int PTLvl0Size = 1 << PTLvl0Bits;
|
||||
private const int PTLvl1Size = 1 << PTLvl1Bits;
|
||||
public const int PageSize = 1 << PTPageBits;
|
||||
|
||||
private const int PTLvl0Mask = PTLvl0Size - 1;
|
||||
private const int PTLvl1Mask = PTLvl1Size - 1;
|
||||
public const int PageMask = PageSize - 1;
|
||||
|
||||
private const int PTLvl0Bit = PTPageBits + PTLvl1Bits;
|
||||
private const int PTLvl1Bit = PTPageBits;
|
||||
|
||||
public AMemory Memory { get; private set; }
|
||||
|
||||
private struct MappedMemory
|
||||
{
|
||||
public long Size;
|
||||
|
||||
public MappedMemory(long Size)
|
||||
{
|
||||
this.Size = Size;
|
||||
}
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<long, MappedMemory> Maps;
|
||||
|
||||
private NvGpuVmmCache Cache;
|
||||
|
||||
private const long PteUnmapped = -1;
|
||||
private const long PteReserved = -2;
|
||||
|
||||
private long[][] PageTable;
|
||||
|
||||
public NvGpuVmm(AMemory Memory)
|
||||
{
|
||||
this.Memory = Memory;
|
||||
|
||||
Maps = new ConcurrentDictionary<long, MappedMemory>();
|
||||
|
||||
Cache = new NvGpuVmmCache();
|
||||
|
||||
PageTable = new long[PTLvl0Size][];
|
||||
}
|
||||
|
||||
public long Map(long PA, long VA, long Size)
|
||||
{
|
||||
lock (PageTable)
|
||||
{
|
||||
for (long Offset = 0; Offset < Size; Offset += PageSize)
|
||||
{
|
||||
if (GetPte(VA + Offset) != PteReserved)
|
||||
{
|
||||
return Map(PA, Size);
|
||||
}
|
||||
}
|
||||
|
||||
for (long Offset = 0; Offset < Size; Offset += PageSize)
|
||||
{
|
||||
SetPte(VA + Offset, PA + Offset);
|
||||
}
|
||||
}
|
||||
|
||||
return VA;
|
||||
}
|
||||
|
||||
public long Map(long PA, long Size)
|
||||
{
|
||||
lock (PageTable)
|
||||
{
|
||||
long VA = GetFreePosition(Size);
|
||||
|
||||
if (VA != -1)
|
||||
{
|
||||
MappedMemory Map = new MappedMemory(Size);
|
||||
|
||||
Maps.AddOrUpdate(VA, Map, (Key, Old) => Map);
|
||||
|
||||
for (long Offset = 0; Offset < Size; Offset += PageSize)
|
||||
{
|
||||
SetPte(VA + Offset, PA + Offset);
|
||||
}
|
||||
}
|
||||
|
||||
return VA;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Unmap(long VA)
|
||||
{
|
||||
if (Maps.TryRemove(VA, out MappedMemory Map))
|
||||
{
|
||||
Free(VA, Map.Size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public long Reserve(long VA, long Size, long Align)
|
||||
{
|
||||
lock (PageTable)
|
||||
{
|
||||
for (long Offset = 0; Offset < Size; Offset += PageSize)
|
||||
{
|
||||
if (IsPageInUse(VA + Offset))
|
||||
{
|
||||
return Reserve(Size, Align);
|
||||
}
|
||||
}
|
||||
|
||||
for (long Offset = 0; Offset < Size; Offset += PageSize)
|
||||
{
|
||||
SetPte(VA + Offset, PteReserved);
|
||||
}
|
||||
}
|
||||
|
||||
return VA;
|
||||
}
|
||||
|
||||
public long Reserve(long Size, long Align)
|
||||
{
|
||||
lock (PageTable)
|
||||
{
|
||||
long Position = GetFreePosition(Size, Align);
|
||||
|
||||
if (Position != -1)
|
||||
{
|
||||
for (long Offset = 0; Offset < Size; Offset += PageSize)
|
||||
{
|
||||
SetPte(Position + Offset, PteReserved);
|
||||
}
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(long VA, long Size)
|
||||
{
|
||||
lock (PageTable)
|
||||
{
|
||||
for (long Offset = 0; Offset < Size; Offset += PageSize)
|
||||
{
|
||||
SetPte(VA + Offset, PteUnmapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long GetFreePosition(long Size, long Align = 1)
|
||||
{
|
||||
long Position = 0;
|
||||
long FreeSize = 0;
|
||||
|
||||
if (Align < 1)
|
||||
{
|
||||
Align = 1;
|
||||
}
|
||||
|
||||
Align = (Align + PageMask) & ~PageMask;
|
||||
|
||||
while (Position + FreeSize < AddrSize)
|
||||
{
|
||||
if (!IsPageInUse(Position + FreeSize))
|
||||
{
|
||||
FreeSize += PageSize;
|
||||
|
||||
if (FreeSize >= Size)
|
||||
{
|
||||
return Position;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Position += FreeSize + PageSize;
|
||||
FreeSize = 0;
|
||||
|
||||
long Remainder = Position % Align;
|
||||
|
||||
if (Remainder != 0)
|
||||
{
|
||||
Position = (Position - Remainder) + Align;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public long GetPhysicalAddress(long VA)
|
||||
{
|
||||
long BasePos = GetPte(VA);
|
||||
|
||||
if (BasePos < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return BasePos + (VA & PageMask);
|
||||
}
|
||||
|
||||
public bool IsRegionFree(long VA, long Size)
|
||||
{
|
||||
for (long Offset = 0; Offset < Size; Offset += PageSize)
|
||||
{
|
||||
if (IsPageInUse(VA + Offset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsPageInUse(long VA)
|
||||
{
|
||||
if (VA >> PTLvl0Bits + PTLvl1Bits + PTPageBits != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
long L0 = (VA >> PTLvl0Bit) & PTLvl0Mask;
|
||||
long L1 = (VA >> PTLvl1Bit) & PTLvl1Mask;
|
||||
|
||||
if (PageTable[L0] == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return PageTable[L0][L1] != PteUnmapped;
|
||||
}
|
||||
|
||||
private long GetPte(long Position)
|
||||
{
|
||||
long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
|
||||
long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
|
||||
|
||||
if (PageTable[L0] == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return PageTable[L0][L1];
|
||||
}
|
||||
|
||||
private void SetPte(long Position, long TgtAddr)
|
||||
{
|
||||
long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
|
||||
long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
|
||||
|
||||
if (PageTable[L0] == null)
|
||||
{
|
||||
PageTable[L0] = new long[PTLvl1Size];
|
||||
|
||||
for (int Index = 0; Index < PTLvl1Size; Index++)
|
||||
{
|
||||
PageTable[L0][Index] = PteUnmapped;
|
||||
}
|
||||
}
|
||||
|
||||
PageTable[L0][L1] = TgtAddr;
|
||||
}
|
||||
|
||||
public bool IsRegionModified(long Position, long Size, NvGpuBufferType BufferType)
|
||||
{
|
||||
long PA = GetPhysicalAddress(Position);
|
||||
|
||||
return Cache.IsRegionModified(Memory, BufferType, Position, PA, Size);
|
||||
}
|
||||
|
||||
public byte ReadByte(long Position)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadByte(Position);
|
||||
}
|
||||
|
||||
public ushort ReadUInt16(long Position)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadUInt16(Position);
|
||||
}
|
||||
|
||||
public uint ReadUInt32(long Position)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadUInt32(Position);
|
||||
}
|
||||
|
||||
public ulong ReadUInt64(long Position)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadUInt64(Position);
|
||||
}
|
||||
|
||||
public sbyte ReadSByte(long Position)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadSByte(Position);
|
||||
}
|
||||
|
||||
public short ReadInt16(long Position)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadInt16(Position);
|
||||
}
|
||||
|
||||
public int ReadInt32(long Position)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadInt32(Position);
|
||||
}
|
||||
|
||||
public long ReadInt64(long Position)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadInt64(Position);
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(long Position, long Size)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
return Memory.ReadBytes(Position, Size);
|
||||
}
|
||||
|
||||
public void WriteByte(long Position, byte Value)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteByte(Position, Value);
|
||||
}
|
||||
|
||||
public void WriteUInt16(long Position, ushort Value)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteUInt16(Position, Value);
|
||||
}
|
||||
|
||||
public void WriteUInt32(long Position, uint Value)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteUInt32(Position, Value);
|
||||
}
|
||||
|
||||
public void WriteUInt64(long Position, ulong Value)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteUInt64(Position, Value);
|
||||
}
|
||||
|
||||
public void WriteSByte(long Position, sbyte Value)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteSByte(Position, Value);
|
||||
}
|
||||
|
||||
public void WriteInt16(long Position, short Value)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteInt16(Position, Value);
|
||||
}
|
||||
|
||||
public void WriteInt32(long Position, int Value)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteInt32(Position, Value);
|
||||
}
|
||||
|
||||
public void WriteInt64(long Position, long Value)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteInt64(Position, Value);
|
||||
}
|
||||
|
||||
public void WriteBytes(long Position, byte[] Data)
|
||||
{
|
||||
Position = GetPhysicalAddress(Position);
|
||||
|
||||
Memory.WriteBytes(Position, Data);
|
||||
}
|
||||
}
|
||||
}
|
209
Ryujinx.HLE/Gpu/NvGpuVmmCache.cs
Normal file
209
Ryujinx.HLE/Gpu/NvGpuVmmCache.cs
Normal file
|
@ -0,0 +1,209 @@
|
|||
using ChocolArm64.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
class NvGpuVmmCache
|
||||
{
|
||||
private const int MaxCpCount = 10000;
|
||||
private const int MaxCpTimeDelta = 60000;
|
||||
|
||||
private class CachedPage
|
||||
{
|
||||
private List<(long Start, long End)> Regions;
|
||||
|
||||
public LinkedListNode<long> Node { get; set; }
|
||||
|
||||
public int Count => Regions.Count;
|
||||
|
||||
public int Timestamp { get; private set; }
|
||||
|
||||
public long PABase { get; private set; }
|
||||
|
||||
public NvGpuBufferType BufferType { get; private set; }
|
||||
|
||||
public CachedPage(long PABase, NvGpuBufferType BufferType)
|
||||
{
|
||||
this.PABase = PABase;
|
||||
this.BufferType = BufferType;
|
||||
|
||||
Regions = new List<(long, long)>();
|
||||
}
|
||||
|
||||
public bool AddRange(long Start, long End)
|
||||
{
|
||||
for (int Index = 0; Index < Regions.Count; Index++)
|
||||
{
|
||||
(long RgStart, long RgEnd) = Regions[Index];
|
||||
|
||||
if (Start >= RgStart && End <= RgEnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Start <= RgEnd && RgStart <= End)
|
||||
{
|
||||
long MinStart = Math.Min(RgStart, Start);
|
||||
long MaxEnd = Math.Max(RgEnd, End);
|
||||
|
||||
Regions[Index] = (MinStart, MaxEnd);
|
||||
|
||||
Timestamp = Environment.TickCount;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Regions.Add((Start, End));
|
||||
|
||||
Timestamp = Environment.TickCount;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<long, CachedPage> Cache;
|
||||
|
||||
private LinkedList<long> SortedCache;
|
||||
|
||||
private int CpCount;
|
||||
|
||||
public NvGpuVmmCache()
|
||||
{
|
||||
Cache = new Dictionary<long, CachedPage>();
|
||||
|
||||
SortedCache = new LinkedList<long>();
|
||||
}
|
||||
|
||||
public bool IsRegionModified(
|
||||
AMemory Memory,
|
||||
NvGpuBufferType BufferType,
|
||||
long VA,
|
||||
long PA,
|
||||
long Size)
|
||||
{
|
||||
ClearCachedPagesIfNeeded();
|
||||
|
||||
long PageSize = Memory.GetHostPageSize();
|
||||
|
||||
long Mask = PageSize - 1;
|
||||
|
||||
long VAEnd = VA + Size;
|
||||
long PAEnd = PA + Size;
|
||||
|
||||
bool RegMod = false;
|
||||
|
||||
while (VA < VAEnd)
|
||||
{
|
||||
long Key = VA & ~Mask;
|
||||
long PABase = PA & ~Mask;
|
||||
|
||||
long VAPgEnd = Math.Min((VA + PageSize) & ~Mask, VAEnd);
|
||||
long PAPgEnd = Math.Min((PA + PageSize) & ~Mask, PAEnd);
|
||||
|
||||
bool IsCached = Cache.TryGetValue(Key, out CachedPage Cp);
|
||||
|
||||
bool PgReset = false;
|
||||
|
||||
if (!IsCached)
|
||||
{
|
||||
Cp = new CachedPage(PABase, BufferType);
|
||||
|
||||
Cache.Add(Key, Cp);
|
||||
}
|
||||
else
|
||||
{
|
||||
CpCount -= Cp.Count;
|
||||
|
||||
SortedCache.Remove(Cp.Node);
|
||||
|
||||
if (Cp.PABase != PABase ||
|
||||
Cp.BufferType != BufferType)
|
||||
{
|
||||
PgReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
PgReset |= Memory.IsRegionModified(PA, PAPgEnd - PA) && IsCached;
|
||||
|
||||
if (PgReset)
|
||||
{
|
||||
Cp = new CachedPage(PABase, BufferType);
|
||||
|
||||
Cache[Key] = Cp;
|
||||
}
|
||||
|
||||
Cp.Node = SortedCache.AddLast(Key);
|
||||
|
||||
RegMod |= Cp.AddRange(VA, VAPgEnd);
|
||||
|
||||
CpCount += Cp.Count;
|
||||
|
||||
VA = VAPgEnd;
|
||||
PA = PAPgEnd;
|
||||
}
|
||||
|
||||
return RegMod;
|
||||
}
|
||||
|
||||
private void ClearCachedPagesIfNeeded()
|
||||
{
|
||||
if (CpCount <= MaxCpCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int Timestamp = Environment.TickCount;
|
||||
|
||||
int TimeDelta;
|
||||
|
||||
do
|
||||
{
|
||||
if (!TryPopOldestCachedPageKey(Timestamp, out long Key))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
CachedPage Cp = Cache[Key];
|
||||
|
||||
Cache.Remove(Key);
|
||||
|
||||
CpCount -= Cp.Count;
|
||||
|
||||
TimeDelta = RingDelta(Cp.Timestamp, Timestamp);
|
||||
}
|
||||
while (CpCount > (MaxCpCount >> 1) || (uint)TimeDelta > (uint)MaxCpTimeDelta);
|
||||
}
|
||||
|
||||
private bool TryPopOldestCachedPageKey(int Timestamp, out long Key)
|
||||
{
|
||||
LinkedListNode<long> Node = SortedCache.First;
|
||||
|
||||
if (Node == null)
|
||||
{
|
||||
Key = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SortedCache.Remove(Node);
|
||||
|
||||
Key = Node.Value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int RingDelta(int Old, int New)
|
||||
{
|
||||
if ((uint)New < (uint)Old)
|
||||
{
|
||||
return New + (~Old + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return New - Old;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
Ryujinx.HLE/Gpu/Texture.cs
Normal file
55
Ryujinx.HLE/Gpu/Texture.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using Ryujinx.Graphics.Gal;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
struct Texture
|
||||
{
|
||||
public long Position { get; private set; }
|
||||
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
public int Pitch { get; private set; }
|
||||
|
||||
public int BlockHeight { get; private set; }
|
||||
|
||||
public TextureSwizzle Swizzle { get; private set; }
|
||||
|
||||
public GalTextureFormat Format { get; private set; }
|
||||
|
||||
public Texture(
|
||||
long Position,
|
||||
int Width,
|
||||
int Height)
|
||||
{
|
||||
this.Position = Position;
|
||||
this.Width = Width;
|
||||
this.Height = Height;
|
||||
|
||||
Pitch = 0;
|
||||
|
||||
BlockHeight = 16;
|
||||
|
||||
Swizzle = TextureSwizzle.BlockLinear;
|
||||
|
||||
Format = GalTextureFormat.A8B8G8R8;
|
||||
}
|
||||
|
||||
public Texture(
|
||||
long Position,
|
||||
int Width,
|
||||
int Height,
|
||||
int Pitch,
|
||||
int BlockHeight,
|
||||
TextureSwizzle Swizzle,
|
||||
GalTextureFormat Format)
|
||||
{
|
||||
this.Position = Position;
|
||||
this.Width = Width;
|
||||
this.Height = Height;
|
||||
this.Pitch = Pitch;
|
||||
this.BlockHeight = BlockHeight;
|
||||
this.Swizzle = Swizzle;
|
||||
this.Format = Format;
|
||||
}
|
||||
}
|
||||
}
|
105
Ryujinx.HLE/Gpu/TextureFactory.cs
Normal file
105
Ryujinx.HLE/Gpu/TextureFactory.cs
Normal file
|
@ -0,0 +1,105 @@
|
|||
using Ryujinx.Graphics.Gal;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
static class TextureFactory
|
||||
{
|
||||
public static GalTexture MakeTexture(NvGpuVmm Vmm, long TicPosition)
|
||||
{
|
||||
int[] Tic = ReadWords(Vmm, TicPosition, 8);
|
||||
|
||||
GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f);
|
||||
|
||||
GalTextureSource XSource = (GalTextureSource)((Tic[0] >> 19) & 7);
|
||||
GalTextureSource YSource = (GalTextureSource)((Tic[0] >> 22) & 7);
|
||||
GalTextureSource ZSource = (GalTextureSource)((Tic[0] >> 25) & 7);
|
||||
GalTextureSource WSource = (GalTextureSource)((Tic[0] >> 28) & 7);
|
||||
|
||||
int Width = (Tic[4] & 0xffff) + 1;
|
||||
int Height = (Tic[5] & 0xffff) + 1;
|
||||
|
||||
return new GalTexture(
|
||||
Width,
|
||||
Height,
|
||||
Format,
|
||||
XSource,
|
||||
YSource,
|
||||
ZSource,
|
||||
WSource);
|
||||
}
|
||||
|
||||
public static byte[] GetTextureData(NvGpuVmm Vmm, long TicPosition)
|
||||
{
|
||||
int[] Tic = ReadWords(Vmm, TicPosition, 8);
|
||||
|
||||
GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f);
|
||||
|
||||
long TextureAddress = (uint)Tic[1];
|
||||
|
||||
TextureAddress |= (long)((ushort)Tic[2]) << 32;
|
||||
|
||||
TextureSwizzle Swizzle = (TextureSwizzle)((Tic[2] >> 21) & 7);
|
||||
|
||||
int Pitch = (Tic[3] & 0xffff) << 5;
|
||||
|
||||
int BlockHeightLog2 = (Tic[3] >> 3) & 7;
|
||||
|
||||
int BlockHeight = 1 << BlockHeightLog2;
|
||||
|
||||
int Width = (Tic[4] & 0xffff) + 1;
|
||||
int Height = (Tic[5] & 0xffff) + 1;
|
||||
|
||||
Texture Texture = new Texture(
|
||||
TextureAddress,
|
||||
Width,
|
||||
Height,
|
||||
Pitch,
|
||||
BlockHeight,
|
||||
Swizzle,
|
||||
Format);
|
||||
|
||||
return TextureReader.Read(Vmm, Texture);
|
||||
}
|
||||
|
||||
public static GalTextureSampler MakeSampler(NvGpu Gpu, NvGpuVmm Vmm, long TscPosition)
|
||||
{
|
||||
int[] Tsc = ReadWords(Vmm, TscPosition, 8);
|
||||
|
||||
GalTextureWrap AddressU = (GalTextureWrap)((Tsc[0] >> 0) & 7);
|
||||
GalTextureWrap AddressV = (GalTextureWrap)((Tsc[0] >> 3) & 7);
|
||||
GalTextureWrap AddressP = (GalTextureWrap)((Tsc[0] >> 6) & 7);
|
||||
|
||||
GalTextureFilter MagFilter = (GalTextureFilter) ((Tsc[1] >> 0) & 3);
|
||||
GalTextureFilter MinFilter = (GalTextureFilter) ((Tsc[1] >> 4) & 3);
|
||||
GalTextureMipFilter MipFilter = (GalTextureMipFilter)((Tsc[1] >> 6) & 3);
|
||||
|
||||
GalColorF BorderColor = new GalColorF(
|
||||
BitConverter.Int32BitsToSingle(Tsc[4]),
|
||||
BitConverter.Int32BitsToSingle(Tsc[5]),
|
||||
BitConverter.Int32BitsToSingle(Tsc[6]),
|
||||
BitConverter.Int32BitsToSingle(Tsc[7]));
|
||||
|
||||
return new GalTextureSampler(
|
||||
AddressU,
|
||||
AddressV,
|
||||
AddressP,
|
||||
MinFilter,
|
||||
MagFilter,
|
||||
MipFilter,
|
||||
BorderColor);
|
||||
}
|
||||
|
||||
private static int[] ReadWords(NvGpuVmm Vmm, long Position, int Count)
|
||||
{
|
||||
int[] Words = new int[Count];
|
||||
|
||||
for (int Index = 0; Index < Count; Index++, Position += 4)
|
||||
{
|
||||
Words[Index] = Vmm.ReadInt32(Position);
|
||||
}
|
||||
|
||||
return Words;
|
||||
}
|
||||
}
|
||||
}
|
75
Ryujinx.HLE/Gpu/TextureHelper.cs
Normal file
75
Ryujinx.HLE/Gpu/TextureHelper.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using ChocolArm64.Memory;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
static class TextureHelper
|
||||
{
|
||||
public static ISwizzle GetSwizzle(Texture Texture, int Width, int Bpp)
|
||||
{
|
||||
switch (Texture.Swizzle)
|
||||
{
|
||||
case TextureSwizzle.Pitch:
|
||||
case TextureSwizzle.PitchColorKey:
|
||||
return new LinearSwizzle(Texture.Pitch, Bpp);
|
||||
|
||||
case TextureSwizzle.BlockLinear:
|
||||
case TextureSwizzle.BlockLinearColorKey:
|
||||
return new BlockLinearSwizzle(Width, Bpp, Texture.BlockHeight);
|
||||
}
|
||||
|
||||
throw new NotImplementedException(Texture.Swizzle.ToString());
|
||||
}
|
||||
|
||||
public static int GetTextureSize(GalTexture Texture)
|
||||
{
|
||||
switch (Texture.Format)
|
||||
{
|
||||
case GalTextureFormat.R32G32B32A32: return Texture.Width * Texture.Height * 16;
|
||||
case GalTextureFormat.R16G16B16A16: return Texture.Width * Texture.Height * 8;
|
||||
case GalTextureFormat.A8B8G8R8: return Texture.Width * Texture.Height * 4;
|
||||
case GalTextureFormat.R32: return Texture.Width * Texture.Height * 4;
|
||||
case GalTextureFormat.A1B5G5R5: return Texture.Width * Texture.Height * 2;
|
||||
case GalTextureFormat.B5G6R5: return Texture.Width * Texture.Height * 2;
|
||||
case GalTextureFormat.G8R8: return Texture.Width * Texture.Height * 2;
|
||||
case GalTextureFormat.R8: return Texture.Width * Texture.Height;
|
||||
|
||||
case GalTextureFormat.BC1:
|
||||
case GalTextureFormat.BC4:
|
||||
{
|
||||
int W = (Texture.Width + 3) / 4;
|
||||
int H = (Texture.Height + 3) / 4;
|
||||
|
||||
return W * H * 8;
|
||||
}
|
||||
|
||||
case GalTextureFormat.BC7U:
|
||||
case GalTextureFormat.BC2:
|
||||
case GalTextureFormat.BC3:
|
||||
case GalTextureFormat.BC5:
|
||||
case GalTextureFormat.Astc2D4x4:
|
||||
{
|
||||
int W = (Texture.Width + 3) / 4;
|
||||
int H = (Texture.Height + 3) / 4;
|
||||
|
||||
return W * H * 16;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotImplementedException(Texture.Format.ToString());
|
||||
}
|
||||
|
||||
public static (AMemory Memory, long Position) GetMemoryAndPosition(
|
||||
IAMemory Memory,
|
||||
long Position)
|
||||
{
|
||||
if (Memory is NvGpuVmm Vmm)
|
||||
{
|
||||
return (Vmm.Memory, Vmm.GetPhysicalAddress(Position));
|
||||
}
|
||||
|
||||
return ((AMemory)Memory, Position);
|
||||
}
|
||||
}
|
||||
}
|
343
Ryujinx.HLE/Gpu/TextureReader.cs
Normal file
343
Ryujinx.HLE/Gpu/TextureReader.cs
Normal file
|
@ -0,0 +1,343 @@
|
|||
using ChocolArm64.Memory;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
static class TextureReader
|
||||
{
|
||||
public static byte[] Read(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
switch (Texture.Format)
|
||||
{
|
||||
case GalTextureFormat.R32G32B32A32: return Read16Bpp (Memory, Texture);
|
||||
case GalTextureFormat.R16G16B16A16: return Read8Bpp (Memory, Texture);
|
||||
case GalTextureFormat.A8B8G8R8: return Read4Bpp (Memory, Texture);
|
||||
case GalTextureFormat.R32: return Read4Bpp (Memory, Texture);
|
||||
case GalTextureFormat.A1B5G5R5: return Read5551 (Memory, Texture);
|
||||
case GalTextureFormat.B5G6R5: return Read565 (Memory, Texture);
|
||||
case GalTextureFormat.G8R8: return Read2Bpp (Memory, Texture);
|
||||
case GalTextureFormat.R8: return Read1Bpp (Memory, Texture);
|
||||
case GalTextureFormat.BC7U: return Read16Bpt4x4(Memory, Texture);
|
||||
case GalTextureFormat.BC1: return Read8Bpt4x4 (Memory, Texture);
|
||||
case GalTextureFormat.BC2: return Read16Bpt4x4(Memory, Texture);
|
||||
case GalTextureFormat.BC3: return Read16Bpt4x4(Memory, Texture);
|
||||
case GalTextureFormat.BC4: return Read8Bpt4x4 (Memory, Texture);
|
||||
case GalTextureFormat.BC5: return Read16Bpt4x4(Memory, Texture);
|
||||
case GalTextureFormat.Astc2D4x4: return Read16Bpt4x4(Memory, Texture);
|
||||
}
|
||||
|
||||
throw new NotImplementedException(Texture.Format.ToString());
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read1Bpp(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = Texture.Width;
|
||||
int Height = Texture.Height;
|
||||
|
||||
byte[] Output = new byte[Width * Height];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 1);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
byte Pixel = CpuMem.ReadByteUnchecked(Position + Offset);
|
||||
|
||||
*(BuffPtr + OutOffs) = Pixel;
|
||||
|
||||
OutOffs++;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read5551(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = Texture.Width;
|
||||
int Height = Texture.Height;
|
||||
|
||||
byte[] Output = new byte[Width * Height * 2];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
uint Pixel = (uint)CpuMem.ReadInt16Unchecked(Position + Offset);
|
||||
|
||||
Pixel = (Pixel & 0x001f) << 11 |
|
||||
(Pixel & 0x03e0) << 1 |
|
||||
(Pixel & 0x7c00) >> 9 |
|
||||
(Pixel & 0x8000) >> 15;
|
||||
|
||||
*(short*)(BuffPtr + OutOffs) = (short)Pixel;
|
||||
|
||||
OutOffs += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read565(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = Texture.Width;
|
||||
int Height = Texture.Height;
|
||||
|
||||
byte[] Output = new byte[Width * Height * 2];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
uint Pixel = (uint)CpuMem.ReadInt16Unchecked(Position + Offset);
|
||||
|
||||
Pixel = (Pixel & 0x001f) << 11 |
|
||||
(Pixel & 0x07e0) |
|
||||
(Pixel & 0xf800) >> 11;
|
||||
|
||||
*(short*)(BuffPtr + OutOffs) = (short)Pixel;
|
||||
|
||||
OutOffs += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read2Bpp(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = Texture.Width;
|
||||
int Height = Texture.Height;
|
||||
|
||||
byte[] Output = new byte[Width * Height * 2];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
short Pixel = CpuMem.ReadInt16Unchecked(Position + Offset);
|
||||
|
||||
*(short*)(BuffPtr + OutOffs) = Pixel;
|
||||
|
||||
OutOffs += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read4Bpp(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = Texture.Width;
|
||||
int Height = Texture.Height;
|
||||
|
||||
byte[] Output = new byte[Width * Height * 4];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
int Pixel = CpuMem.ReadInt32Unchecked(Position + Offset);
|
||||
|
||||
*(int*)(BuffPtr + OutOffs) = Pixel;
|
||||
|
||||
OutOffs += 4;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read8Bpp(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = Texture.Width;
|
||||
int Height = Texture.Height;
|
||||
|
||||
byte[] Output = new byte[Width * Height * 8];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
long Pixel = CpuMem.ReadInt64Unchecked(Position + Offset);
|
||||
|
||||
*(long*)(BuffPtr + OutOffs) = Pixel;
|
||||
|
||||
OutOffs += 8;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read16Bpp(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = Texture.Width;
|
||||
int Height = Texture.Height;
|
||||
|
||||
byte[] Output = new byte[Width * Height * 16];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
long PxLow = CpuMem.ReadInt64Unchecked(Position + Offset + 0);
|
||||
long PxHigh = CpuMem.ReadInt64Unchecked(Position + Offset + 8);
|
||||
|
||||
*(long*)(BuffPtr + OutOffs + 0) = PxLow;
|
||||
*(long*)(BuffPtr + OutOffs + 8) = PxHigh;
|
||||
|
||||
OutOffs += 16;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read8Bpt4x4(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = (Texture.Width + 3) / 4;
|
||||
int Height = (Texture.Height + 3) / 4;
|
||||
|
||||
byte[] Output = new byte[Width * Height * 8];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
long Tile = CpuMem.ReadInt64Unchecked(Position + Offset);
|
||||
|
||||
*(long*)(BuffPtr + OutOffs) = Tile;
|
||||
|
||||
OutOffs += 8;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
|
||||
private unsafe static byte[] Read16Bpt4x4(IAMemory Memory, Texture Texture)
|
||||
{
|
||||
int Width = (Texture.Width + 3) / 4;
|
||||
int Height = (Texture.Height + 3) / 4;
|
||||
|
||||
byte[] Output = new byte[Width * Height * 16];
|
||||
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Output)
|
||||
{
|
||||
long OutOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
long Tile0 = CpuMem.ReadInt64Unchecked(Position + Offset + 0);
|
||||
long Tile1 = CpuMem.ReadInt64Unchecked(Position + Offset + 8);
|
||||
|
||||
*(long*)(BuffPtr + OutOffs + 0) = Tile0;
|
||||
*(long*)(BuffPtr + OutOffs + 8) = Tile1;
|
||||
|
||||
OutOffs += 16;
|
||||
}
|
||||
}
|
||||
|
||||
return Output;
|
||||
}
|
||||
}
|
||||
}
|
11
Ryujinx.HLE/Gpu/TextureSwizzle.cs
Normal file
11
Ryujinx.HLE/Gpu/TextureSwizzle.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
enum TextureSwizzle
|
||||
{
|
||||
_1dBuffer = 0,
|
||||
PitchColorKey = 1,
|
||||
Pitch = 2,
|
||||
BlockLinear = 3,
|
||||
BlockLinearColorKey = 4
|
||||
}
|
||||
}
|
55
Ryujinx.HLE/Gpu/TextureWriter.cs
Normal file
55
Ryujinx.HLE/Gpu/TextureWriter.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using ChocolArm64.Memory;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Gpu
|
||||
{
|
||||
static class TextureWriter
|
||||
{
|
||||
public static void Write(
|
||||
IAMemory Memory,
|
||||
Texture Texture,
|
||||
byte[] Data,
|
||||
int Width,
|
||||
int Height)
|
||||
{
|
||||
switch (Texture.Format)
|
||||
{
|
||||
case GalTextureFormat.A8B8G8R8: Write4Bpp(Memory, Texture, Data, Width, Height); break;
|
||||
|
||||
default: throw new NotImplementedException(Texture.Format.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe static void Write4Bpp(
|
||||
IAMemory Memory,
|
||||
Texture Texture,
|
||||
byte[] Data,
|
||||
int Width,
|
||||
int Height)
|
||||
{
|
||||
ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4);
|
||||
|
||||
(AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
|
||||
Memory,
|
||||
Texture.Position);
|
||||
|
||||
fixed (byte* BuffPtr = Data)
|
||||
{
|
||||
long InOffs = 0;
|
||||
|
||||
for (int Y = 0; Y < Height; Y++)
|
||||
for (int X = 0; X < Width; X++)
|
||||
{
|
||||
long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
|
||||
|
||||
int Pixel = *(int*)(BuffPtr + InOffs);
|
||||
|
||||
CpuMem.WriteInt32Unchecked(Position + Offset, Pixel);
|
||||
|
||||
InOffs += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
279
Ryujinx.HLE/Hid/Hid.cs
Normal file
279
Ryujinx.HLE/Hid/Hid.cs
Normal file
|
@ -0,0 +1,279 @@
|
|||
using ChocolArm64.Memory;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
public class Hid
|
||||
{
|
||||
/*
|
||||
* Reference:
|
||||
* https://github.com/reswitched/libtransistor/blob/development/lib/hid.c
|
||||
* https://github.com/reswitched/libtransistor/blob/development/include/libtransistor/hid.h
|
||||
* https://github.com/switchbrew/libnx/blob/master/nx/source/services/hid.c
|
||||
* https://github.com/switchbrew/libnx/blob/master/nx/include/switch/services/hid.h
|
||||
*/
|
||||
|
||||
private const int HidHeaderSize = 0x400;
|
||||
private const int HidTouchScreenSize = 0x3000;
|
||||
private const int HidMouseSize = 0x400;
|
||||
private const int HidKeyboardSize = 0x400;
|
||||
private const int HidUnkSection1Size = 0x400;
|
||||
private const int HidUnkSection2Size = 0x400;
|
||||
private const int HidUnkSection3Size = 0x400;
|
||||
private const int HidUnkSection4Size = 0x400;
|
||||
private const int HidUnkSection5Size = 0x200;
|
||||
private const int HidUnkSection6Size = 0x200;
|
||||
private const int HidUnkSection7Size = 0x200;
|
||||
private const int HidUnkSection8Size = 0x800;
|
||||
private const int HidControllerSerialsSize = 0x4000;
|
||||
private const int HidControllersSize = 0x32000;
|
||||
private const int HidUnkSection9Size = 0x800;
|
||||
|
||||
private const int HidTouchHeaderSize = 0x28;
|
||||
private const int HidTouchEntrySize = 0x298;
|
||||
|
||||
private const int HidTouchEntryHeaderSize = 0x10;
|
||||
private const int HidTouchEntryTouchSize = 0x28;
|
||||
|
||||
private const int HidControllerSize = 0x5000;
|
||||
private const int HidControllerHeaderSize = 0x28;
|
||||
private const int HidControllerLayoutsSize = 0x350;
|
||||
|
||||
private const int HidControllersLayoutHeaderSize = 0x20;
|
||||
private const int HidControllersInputEntrySize = 0x30;
|
||||
|
||||
private const int HidHeaderOffset = 0;
|
||||
private const int HidTouchScreenOffset = HidHeaderOffset + HidHeaderSize;
|
||||
private const int HidMouseOffset = HidTouchScreenOffset + HidTouchScreenSize;
|
||||
private const int HidKeyboardOffset = HidMouseOffset + HidMouseSize;
|
||||
private const int HidUnkSection1Offset = HidKeyboardOffset + HidKeyboardSize;
|
||||
private const int HidUnkSection2Offset = HidUnkSection1Offset + HidUnkSection1Size;
|
||||
private const int HidUnkSection3Offset = HidUnkSection2Offset + HidUnkSection2Size;
|
||||
private const int HidUnkSection4Offset = HidUnkSection3Offset + HidUnkSection3Size;
|
||||
private const int HidUnkSection5Offset = HidUnkSection4Offset + HidUnkSection4Size;
|
||||
private const int HidUnkSection6Offset = HidUnkSection5Offset + HidUnkSection5Size;
|
||||
private const int HidUnkSection7Offset = HidUnkSection6Offset + HidUnkSection6Size;
|
||||
private const int HidUnkSection8Offset = HidUnkSection7Offset + HidUnkSection7Size;
|
||||
private const int HidControllerSerialsOffset = HidUnkSection8Offset + HidUnkSection8Size;
|
||||
private const int HidControllersOffset = HidControllerSerialsOffset + HidControllerSerialsSize;
|
||||
private const int HidUnkSection9Offset = HidControllersOffset + HidControllersSize;
|
||||
|
||||
private const int HidEntryCount = 17;
|
||||
|
||||
private Logger Log;
|
||||
|
||||
private object ShMemLock;
|
||||
|
||||
private (AMemory, long)[] ShMemPositions;
|
||||
|
||||
public Hid(Logger Log)
|
||||
{
|
||||
this.Log = Log;
|
||||
|
||||
ShMemLock = new object();
|
||||
|
||||
ShMemPositions = new (AMemory, long)[0];
|
||||
}
|
||||
|
||||
internal void ShMemMap(object sender, EventArgs e)
|
||||
{
|
||||
HSharedMem SharedMem = (HSharedMem)sender;
|
||||
|
||||
lock (ShMemLock)
|
||||
{
|
||||
ShMemPositions = SharedMem.GetVirtualPositions();
|
||||
|
||||
(AMemory Memory, long Position) = ShMemPositions[ShMemPositions.Length - 1];
|
||||
|
||||
for (long Offset = 0; Offset < Horizon.HidSize; Offset += 8)
|
||||
{
|
||||
Memory.WriteInt64Unchecked(Position + Offset, 0);
|
||||
}
|
||||
|
||||
Log.PrintInfo(LogClass.Hid, $"HID shared memory successfully mapped to 0x{Position:x16}!");
|
||||
|
||||
Init(Memory, Position);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ShMemUnmap(object sender, EventArgs e)
|
||||
{
|
||||
HSharedMem SharedMem = (HSharedMem)sender;
|
||||
|
||||
lock (ShMemLock)
|
||||
{
|
||||
ShMemPositions = SharedMem.GetVirtualPositions();
|
||||
}
|
||||
}
|
||||
|
||||
private void Init(AMemory Memory, long Position)
|
||||
{
|
||||
InitializeJoyconPair(
|
||||
Memory,
|
||||
Position,
|
||||
JoyConColor.Body_Neon_Red,
|
||||
JoyConColor.Buttons_Neon_Red,
|
||||
JoyConColor.Body_Neon_Blue,
|
||||
JoyConColor.Buttons_Neon_Blue);
|
||||
}
|
||||
|
||||
private void InitializeJoyconPair(
|
||||
AMemory Memory,
|
||||
long Position,
|
||||
JoyConColor LeftColorBody,
|
||||
JoyConColor LeftColorButtons,
|
||||
JoyConColor RightColorBody,
|
||||
JoyConColor RightColorButtons)
|
||||
{
|
||||
long BaseControllerOffset = Position + HidControllersOffset + 8 * HidControllerSize;
|
||||
|
||||
HidControllerType Type =
|
||||
HidControllerType.ControllerType_Handheld |
|
||||
HidControllerType.ControllerType_JoyconPair;
|
||||
|
||||
bool IsHalf = false;
|
||||
|
||||
HidControllerColorDesc SingleColorDesc =
|
||||
HidControllerColorDesc.ColorDesc_ColorsNonexistent;
|
||||
|
||||
JoyConColor SingleColorBody = JoyConColor.Black;
|
||||
JoyConColor SingleColorButtons = JoyConColor.Black;
|
||||
|
||||
HidControllerColorDesc SplitColorDesc = 0;
|
||||
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x0, (int)Type);
|
||||
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x4, IsHalf ? 1 : 0);
|
||||
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x8, (int)SingleColorDesc);
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0xc, (int)SingleColorBody);
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x10, (int)SingleColorButtons);
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x14, (int)SplitColorDesc);
|
||||
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x18, (int)LeftColorBody);
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x1c, (int)LeftColorButtons);
|
||||
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x20, (int)RightColorBody);
|
||||
Memory.WriteInt32Unchecked(BaseControllerOffset + 0x24, (int)RightColorButtons);
|
||||
}
|
||||
|
||||
public void SetJoyconButton(
|
||||
HidControllerId ControllerId,
|
||||
HidControllerLayouts ControllerLayout,
|
||||
HidControllerButtons Buttons,
|
||||
HidJoystickPosition LeftStick,
|
||||
HidJoystickPosition RightStick)
|
||||
{
|
||||
lock (ShMemLock)
|
||||
{
|
||||
foreach ((AMemory Memory, long Position) in ShMemPositions)
|
||||
{
|
||||
long ControllerOffset = Position + HidControllersOffset;
|
||||
|
||||
ControllerOffset += (int)ControllerId * HidControllerSize;
|
||||
|
||||
ControllerOffset += HidControllerHeaderSize;
|
||||
|
||||
ControllerOffset += (int)ControllerLayout * HidControllerLayoutsSize;
|
||||
|
||||
long LastEntry = Memory.ReadInt64Unchecked(ControllerOffset + 0x10);
|
||||
|
||||
long CurrEntry = (LastEntry + 1) % HidEntryCount;
|
||||
|
||||
long Timestamp = GetTimestamp();
|
||||
|
||||
Memory.WriteInt64Unchecked(ControllerOffset + 0x0, Timestamp);
|
||||
Memory.WriteInt64Unchecked(ControllerOffset + 0x8, HidEntryCount);
|
||||
Memory.WriteInt64Unchecked(ControllerOffset + 0x10, CurrEntry);
|
||||
Memory.WriteInt64Unchecked(ControllerOffset + 0x18, HidEntryCount - 1);
|
||||
|
||||
ControllerOffset += HidControllersLayoutHeaderSize;
|
||||
|
||||
long LastEntryOffset = ControllerOffset + LastEntry * HidControllersInputEntrySize;
|
||||
|
||||
ControllerOffset += CurrEntry * HidControllersInputEntrySize;
|
||||
|
||||
long SampleCounter = Memory.ReadInt64Unchecked(LastEntryOffset) + 1;
|
||||
|
||||
Memory.WriteInt64Unchecked(ControllerOffset + 0x0, SampleCounter);
|
||||
Memory.WriteInt64Unchecked(ControllerOffset + 0x8, SampleCounter);
|
||||
|
||||
Memory.WriteInt64Unchecked(ControllerOffset + 0x10, (uint)Buttons);
|
||||
|
||||
Memory.WriteInt32Unchecked(ControllerOffset + 0x18, LeftStick.DX);
|
||||
Memory.WriteInt32Unchecked(ControllerOffset + 0x1c, LeftStick.DY);
|
||||
|
||||
Memory.WriteInt32Unchecked(ControllerOffset + 0x20, RightStick.DX);
|
||||
Memory.WriteInt32Unchecked(ControllerOffset + 0x24, RightStick.DY);
|
||||
|
||||
Memory.WriteInt64Unchecked(ControllerOffset + 0x28,
|
||||
(uint)HidControllerConnState.Controller_State_Connected |
|
||||
(uint)HidControllerConnState.Controller_State_Wired);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTouchPoints(params HidTouchPoint[] Points)
|
||||
{
|
||||
lock (ShMemLock)
|
||||
{
|
||||
foreach ((AMemory Memory, long Position) in ShMemPositions)
|
||||
{
|
||||
long TouchScreenOffset = Position + HidTouchScreenOffset;
|
||||
|
||||
long LastEntry = Memory.ReadInt64Unchecked(TouchScreenOffset + 0x10);
|
||||
|
||||
long CurrEntry = (LastEntry + 1) % HidEntryCount;
|
||||
|
||||
long Timestamp = GetTimestamp();
|
||||
|
||||
Memory.WriteInt64Unchecked(TouchScreenOffset + 0x0, Timestamp);
|
||||
Memory.WriteInt64Unchecked(TouchScreenOffset + 0x8, HidEntryCount);
|
||||
Memory.WriteInt64Unchecked(TouchScreenOffset + 0x10, CurrEntry);
|
||||
Memory.WriteInt64Unchecked(TouchScreenOffset + 0x18, HidEntryCount - 1);
|
||||
Memory.WriteInt64Unchecked(TouchScreenOffset + 0x20, Timestamp);
|
||||
|
||||
long TouchEntryOffset = TouchScreenOffset + HidTouchHeaderSize;
|
||||
|
||||
long LastEntryOffset = TouchEntryOffset + LastEntry * HidTouchEntrySize;
|
||||
|
||||
long SampleCounter = Memory.ReadInt64Unchecked(LastEntryOffset) + 1;
|
||||
|
||||
TouchEntryOffset += CurrEntry * HidTouchEntrySize;
|
||||
|
||||
Memory.WriteInt64Unchecked(TouchEntryOffset + 0x0, SampleCounter);
|
||||
Memory.WriteInt64Unchecked(TouchEntryOffset + 0x8, Points.Length);
|
||||
|
||||
TouchEntryOffset += HidTouchEntryHeaderSize;
|
||||
|
||||
const int Padding = 0;
|
||||
|
||||
int Index = 0;
|
||||
|
||||
foreach (HidTouchPoint Point in Points)
|
||||
{
|
||||
Memory.WriteInt64Unchecked(TouchEntryOffset + 0x0, Timestamp);
|
||||
Memory.WriteInt32Unchecked(TouchEntryOffset + 0x8, Padding);
|
||||
Memory.WriteInt32Unchecked(TouchEntryOffset + 0xc, Index++);
|
||||
Memory.WriteInt32Unchecked(TouchEntryOffset + 0x10, Point.X);
|
||||
Memory.WriteInt32Unchecked(TouchEntryOffset + 0x14, Point.Y);
|
||||
Memory.WriteInt32Unchecked(TouchEntryOffset + 0x18, Point.DiameterX);
|
||||
Memory.WriteInt32Unchecked(TouchEntryOffset + 0x1c, Point.DiameterY);
|
||||
Memory.WriteInt32Unchecked(TouchEntryOffset + 0x20, Point.Angle);
|
||||
Memory.WriteInt32Unchecked(TouchEntryOffset + 0x24, Padding);
|
||||
|
||||
TouchEntryOffset += HidTouchEntryTouchSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long GetTimestamp()
|
||||
{
|
||||
return (long)((ulong)Environment.TickCount * 19_200);
|
||||
}
|
||||
}
|
||||
}
|
35
Ryujinx.HLE/Hid/HidControllerButtons.cs
Normal file
35
Ryujinx.HLE/Hid/HidControllerButtons.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
[Flags]
|
||||
public enum HidControllerButtons
|
||||
{
|
||||
KEY_A = (1 << 0),
|
||||
KEY_B = (1 << 1),
|
||||
KEY_X = (1 << 2),
|
||||
KEY_Y = (1 << 3),
|
||||
KEY_LSTICK = (1 << 4),
|
||||
KEY_RSTICK = (1 << 5),
|
||||
KEY_L = (1 << 6),
|
||||
KEY_R = (1 << 7),
|
||||
KEY_ZL = (1 << 8),
|
||||
KEY_ZR = (1 << 9),
|
||||
KEY_PLUS = (1 << 10),
|
||||
KEY_MINUS = (1 << 11),
|
||||
KEY_DLEFT = (1 << 12),
|
||||
KEY_DUP = (1 << 13),
|
||||
KEY_DRIGHT = (1 << 14),
|
||||
KEY_DDOWN = (1 << 15),
|
||||
KEY_LSTICK_LEFT = (1 << 16),
|
||||
KEY_LSTICK_UP = (1 << 17),
|
||||
KEY_LSTICK_RIGHT = (1 << 18),
|
||||
KEY_LSTICK_DOWN = (1 << 19),
|
||||
KEY_RSTICK_LEFT = (1 << 20),
|
||||
KEY_RSTICK_UP = (1 << 21),
|
||||
KEY_RSTICK_RIGHT = (1 << 22),
|
||||
KEY_RSTICK_DOWN = (1 << 23),
|
||||
KEY_SL = (1 << 24),
|
||||
KEY_SR = (1 << 25)
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/Hid/HidControllerColorDesc.cs
Normal file
10
Ryujinx.HLE/Hid/HidControllerColorDesc.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
[Flags]
|
||||
public enum HidControllerColorDesc
|
||||
{
|
||||
ColorDesc_ColorsNonexistent = (1 << 1)
|
||||
}
|
||||
}
|
11
Ryujinx.HLE/Hid/HidControllerConnState.cs
Normal file
11
Ryujinx.HLE/Hid/HidControllerConnState.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
[Flags]
|
||||
public enum HidControllerConnState
|
||||
{
|
||||
Controller_State_Connected = (1 << 0),
|
||||
Controller_State_Wired = (1 << 1)
|
||||
}
|
||||
}
|
16
Ryujinx.HLE/Hid/HidControllerId.cs
Normal file
16
Ryujinx.HLE/Hid/HidControllerId.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
public enum HidControllerId
|
||||
{
|
||||
CONTROLLER_PLAYER_1 = 0,
|
||||
CONTROLLER_PLAYER_2 = 1,
|
||||
CONTROLLER_PLAYER_3 = 2,
|
||||
CONTROLLER_PLAYER_4 = 3,
|
||||
CONTROLLER_PLAYER_5 = 4,
|
||||
CONTROLLER_PLAYER_6 = 5,
|
||||
CONTROLLER_PLAYER_7 = 6,
|
||||
CONTROLLER_PLAYER_8 = 7,
|
||||
CONTROLLER_HANDHELD = 8,
|
||||
CONTROLLER_UNKNOWN = 9
|
||||
}
|
||||
}
|
13
Ryujinx.HLE/Hid/HidControllerLayouts.cs
Normal file
13
Ryujinx.HLE/Hid/HidControllerLayouts.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
public enum HidControllerLayouts
|
||||
{
|
||||
Pro_Controller = 0,
|
||||
Handheld_Joined = 1,
|
||||
Joined = 2,
|
||||
Left = 3,
|
||||
Right = 4,
|
||||
Main_No_Analog = 5,
|
||||
Main = 6
|
||||
}
|
||||
}
|
14
Ryujinx.HLE/Hid/HidControllerType.cs
Normal file
14
Ryujinx.HLE/Hid/HidControllerType.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
[Flags]
|
||||
public enum HidControllerType
|
||||
{
|
||||
ControllerType_ProController = (1 << 0),
|
||||
ControllerType_Handheld = (1 << 1),
|
||||
ControllerType_JoyconPair = (1 << 2),
|
||||
ControllerType_JoyconLeft = (1 << 3),
|
||||
ControllerType_JoyconRight = (1 << 4)
|
||||
}
|
||||
}
|
8
Ryujinx.HLE/Hid/HidJoystickPosition.cs
Normal file
8
Ryujinx.HLE/Hid/HidJoystickPosition.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
public struct HidJoystickPosition
|
||||
{
|
||||
public int DX;
|
||||
public int DY;
|
||||
}
|
||||
}
|
11
Ryujinx.HLE/Hid/HidTouchPoint.cs
Normal file
11
Ryujinx.HLE/Hid/HidTouchPoint.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
public struct HidTouchPoint
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public int DiameterX;
|
||||
public int DiameterY;
|
||||
public int Angle;
|
||||
}
|
||||
}
|
45
Ryujinx.HLE/Hid/JoyCon.cs
Normal file
45
Ryujinx.HLE/Hid/JoyCon.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
//TODO: This is only used by Config, it doesn't belong to Core.
|
||||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
public struct JoyConLeft
|
||||
{
|
||||
public int StickUp;
|
||||
public int StickDown;
|
||||
public int StickLeft;
|
||||
public int StickRight;
|
||||
public int StickButton;
|
||||
public int DPadUp;
|
||||
public int DPadDown;
|
||||
public int DPadLeft;
|
||||
public int DPadRight;
|
||||
public int ButtonMinus;
|
||||
public int ButtonL;
|
||||
public int ButtonZL;
|
||||
public int ButtonSL;
|
||||
public int ButtonSR;
|
||||
}
|
||||
|
||||
public struct JoyConRight
|
||||
{
|
||||
public int StickUp;
|
||||
public int StickDown;
|
||||
public int StickLeft;
|
||||
public int StickRight;
|
||||
public int StickButton;
|
||||
public int ButtonA;
|
||||
public int ButtonB;
|
||||
public int ButtonX;
|
||||
public int ButtonY;
|
||||
public int ButtonPlus;
|
||||
public int ButtonR;
|
||||
public int ButtonZR;
|
||||
public int ButtonSL;
|
||||
public int ButtonSR;
|
||||
}
|
||||
|
||||
public struct JoyCon
|
||||
{
|
||||
public JoyConLeft Left;
|
||||
public JoyConRight Right;
|
||||
}
|
||||
}
|
23
Ryujinx.HLE/Hid/JoyConColor.cs
Normal file
23
Ryujinx.HLE/Hid/JoyConColor.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
namespace Ryujinx.HLE.Input
|
||||
{
|
||||
public enum JoyConColor //Thanks to CTCaer
|
||||
{
|
||||
Black = 0,
|
||||
|
||||
Body_Grey = 0x828282,
|
||||
Body_Neon_Blue = 0x0AB9E6,
|
||||
Body_Neon_Red = 0xFF3C28,
|
||||
Body_Neon_Yellow = 0xE6FF00,
|
||||
Body_Neon_Pink = 0xFF3278,
|
||||
Body_Neon_Green = 0x1EDC00,
|
||||
Body_Red = 0xE10F00,
|
||||
|
||||
Buttons_Grey = 0x0F0F0F,
|
||||
Buttons_Neon_Blue = 0x001E1E,
|
||||
Buttons_Neon_Red = 0x1E0A0A,
|
||||
Buttons_Neon_Yellow = 0x142800,
|
||||
Buttons_Neon_Pink = 0x28001E,
|
||||
Buttons_Neon_Green = 0x002800,
|
||||
Buttons_Red = 0x280A0A
|
||||
}
|
||||
}
|
78
Ryujinx.HLE/Loaders/Compression/Lz4.cs
Normal file
78
Ryujinx.HLE/Loaders/Compression/Lz4.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Compression
|
||||
{
|
||||
static class Lz4
|
||||
{
|
||||
public static byte[] Decompress(byte[] Cmp, int DecLength)
|
||||
{
|
||||
byte[] Dec = new byte[DecLength];
|
||||
|
||||
int CmpPos = 0;
|
||||
int DecPos = 0;
|
||||
|
||||
int GetLength(int Length)
|
||||
{
|
||||
byte Sum;
|
||||
|
||||
if (Length == 0xf)
|
||||
{
|
||||
do
|
||||
{
|
||||
Length += (Sum = Cmp[CmpPos++]);
|
||||
}
|
||||
while (Sum == 0xff);
|
||||
}
|
||||
|
||||
return Length;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
byte Token = Cmp[CmpPos++];
|
||||
|
||||
int EncCount = (Token >> 0) & 0xf;
|
||||
int LitCount = (Token >> 4) & 0xf;
|
||||
|
||||
//Copy literal chunck
|
||||
LitCount = GetLength(LitCount);
|
||||
|
||||
Buffer.BlockCopy(Cmp, CmpPos, Dec, DecPos, LitCount);
|
||||
|
||||
CmpPos += LitCount;
|
||||
DecPos += LitCount;
|
||||
|
||||
if (CmpPos >= Cmp.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//Copy compressed chunck
|
||||
int Back = Cmp[CmpPos++] << 0 |
|
||||
Cmp[CmpPos++] << 8;
|
||||
|
||||
EncCount = GetLength(EncCount) + 4;
|
||||
|
||||
int EncPos = DecPos - Back;
|
||||
|
||||
if (EncCount <= Back)
|
||||
{
|
||||
Buffer.BlockCopy(Dec, EncPos, Dec, DecPos, EncCount);
|
||||
|
||||
DecPos += EncCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (EncCount-- > 0)
|
||||
{
|
||||
Dec[DecPos++] = Dec[EncPos++];
|
||||
}
|
||||
}
|
||||
}
|
||||
while (CmpPos < Cmp.Length &&
|
||||
DecPos < Dec.Length);
|
||||
|
||||
return Dec;
|
||||
}
|
||||
}
|
||||
}
|
15
Ryujinx.HLE/Loaders/ElfDyn.cs
Normal file
15
Ryujinx.HLE/Loaders/ElfDyn.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
struct ElfDyn
|
||||
{
|
||||
public ElfDynTag Tag { get; private set; }
|
||||
|
||||
public long Value { get; private set; }
|
||||
|
||||
public ElfDyn(ElfDynTag Tag, long Value)
|
||||
{
|
||||
this.Tag = Tag;
|
||||
this.Value = Value;
|
||||
}
|
||||
}
|
||||
}
|
72
Ryujinx.HLE/Loaders/ElfDynTag.cs
Normal file
72
Ryujinx.HLE/Loaders/ElfDynTag.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
enum ElfDynTag
|
||||
{
|
||||
DT_NULL = 0,
|
||||
DT_NEEDED = 1,
|
||||
DT_PLTRELSZ = 2,
|
||||
DT_PLTGOT = 3,
|
||||
DT_HASH = 4,
|
||||
DT_STRTAB = 5,
|
||||
DT_SYMTAB = 6,
|
||||
DT_RELA = 7,
|
||||
DT_RELASZ = 8,
|
||||
DT_RELAENT = 9,
|
||||
DT_STRSZ = 10,
|
||||
DT_SYMENT = 11,
|
||||
DT_INIT = 12,
|
||||
DT_FINI = 13,
|
||||
DT_SONAME = 14,
|
||||
DT_RPATH = 15,
|
||||
DT_SYMBOLIC = 16,
|
||||
DT_REL = 17,
|
||||
DT_RELSZ = 18,
|
||||
DT_RELENT = 19,
|
||||
DT_PLTREL = 20,
|
||||
DT_DEBUG = 21,
|
||||
DT_TEXTREL = 22,
|
||||
DT_JMPREL = 23,
|
||||
DT_BIND_NOW = 24,
|
||||
DT_INIT_ARRAY = 25,
|
||||
DT_FINI_ARRAY = 26,
|
||||
DT_INIT_ARRAYSZ = 27,
|
||||
DT_FINI_ARRAYSZ = 28,
|
||||
DT_RUNPATH = 29,
|
||||
DT_FLAGS = 30,
|
||||
DT_ENCODING = 32,
|
||||
DT_PREINIT_ARRAY = 32,
|
||||
DT_PREINIT_ARRAYSZ = 33,
|
||||
DT_GNU_PRELINKED = 0x6ffffdf5,
|
||||
DT_GNU_CONFLICTSZ = 0x6ffffdf6,
|
||||
DT_GNU_LIBLISTSZ = 0x6ffffdf7,
|
||||
DT_CHECKSUM = 0x6ffffdf8,
|
||||
DT_PLTPADSZ = 0x6ffffdf9,
|
||||
DT_MOVEENT = 0x6ffffdfa,
|
||||
DT_MOVESZ = 0x6ffffdfb,
|
||||
DT_FEATURE_1 = 0x6ffffdfc,
|
||||
DT_POSFLAG_1 = 0x6ffffdfd,
|
||||
DT_SYMINSZ = 0x6ffffdfe,
|
||||
DT_SYMINENT = 0x6ffffdff,
|
||||
DT_GNU_HASH = 0x6ffffef5,
|
||||
DT_TLSDESC_PLT = 0x6ffffef6,
|
||||
DT_TLSDESC_GOT = 0x6ffffef7,
|
||||
DT_GNU_CONFLICT = 0x6ffffef8,
|
||||
DT_GNU_LIBLIST = 0x6ffffef9,
|
||||
DT_CONFIG = 0x6ffffefa,
|
||||
DT_DEPAUDIT = 0x6ffffefb,
|
||||
DT_AUDIT = 0x6ffffefc,
|
||||
DT_PLTPAD = 0x6ffffefd,
|
||||
DT_MOVETAB = 0x6ffffefe,
|
||||
DT_SYMINFO = 0x6ffffeff,
|
||||
DT_VERSYM = 0x6ffffff0,
|
||||
DT_RELACOUNT = 0x6ffffff9,
|
||||
DT_RELCOUNT = 0x6ffffffa,
|
||||
DT_FLAGS_1 = 0x6ffffffb,
|
||||
DT_VERDEF = 0x6ffffffc,
|
||||
DT_VERDEFNUM = 0x6ffffffd,
|
||||
DT_VERNEED = 0x6ffffffe,
|
||||
DT_VERNEEDNUM = 0x6fffffff,
|
||||
DT_AUXILIARY = 0x7ffffffd,
|
||||
DT_FILTER = 0x7fffffff
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/Loaders/ElfRel.cs
Normal file
19
Ryujinx.HLE/Loaders/ElfRel.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
struct ElfRel
|
||||
{
|
||||
public long Offset { get; private set; }
|
||||
public long Addend { get; private set; }
|
||||
|
||||
public ElfSym Symbol { get; private set; }
|
||||
public ElfRelType Type { get; private set; }
|
||||
|
||||
public ElfRel(long Offset, long Addend, ElfSym Symbol, ElfRelType Type)
|
||||
{
|
||||
this.Offset = Offset;
|
||||
this.Addend = Addend;
|
||||
this.Symbol = Symbol;
|
||||
this.Type = Type;
|
||||
}
|
||||
}
|
||||
}
|
128
Ryujinx.HLE/Loaders/ElfRelType.cs
Normal file
128
Ryujinx.HLE/Loaders/ElfRelType.cs
Normal file
|
@ -0,0 +1,128 @@
|
|||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
enum ElfRelType
|
||||
{
|
||||
R_AARCH64_NONE = 0,
|
||||
R_AARCH64_ABS64 = 257,
|
||||
R_AARCH64_ABS32 = 258,
|
||||
R_AARCH64_ABS16 = 259,
|
||||
R_AARCH64_PREL64 = 260,
|
||||
R_AARCH64_PREL32 = 261,
|
||||
R_AARCH64_PREL16 = 262,
|
||||
R_AARCH64_MOVW_UABS_G0 = 263,
|
||||
R_AARCH64_MOVW_UABS_G0_NC = 264,
|
||||
R_AARCH64_MOVW_UABS_G1 = 265,
|
||||
R_AARCH64_MOVW_UABS_G1_NC = 266,
|
||||
R_AARCH64_MOVW_UABS_G2 = 267,
|
||||
R_AARCH64_MOVW_UABS_G2_NC = 268,
|
||||
R_AARCH64_MOVW_UABS_G3 = 269,
|
||||
R_AARCH64_MOVW_SABS_G0 = 270,
|
||||
R_AARCH64_MOVW_SABS_G1 = 271,
|
||||
R_AARCH64_MOVW_SABS_G2 = 272,
|
||||
R_AARCH64_LD_PREL_LO19 = 273,
|
||||
R_AARCH64_ADR_PREL_LO21 = 274,
|
||||
R_AARCH64_ADR_PREL_PG_HI21 = 275,
|
||||
R_AARCH64_ADR_PREL_PG_HI21_NC = 276,
|
||||
R_AARCH64_ADD_ABS_LO12_NC = 277,
|
||||
R_AARCH64_LDST8_ABS_LO12_NC = 278,
|
||||
R_AARCH64_TSTBR14 = 279,
|
||||
R_AARCH64_CONDBR19 = 280,
|
||||
R_AARCH64_JUMP26 = 282,
|
||||
R_AARCH64_CALL26 = 283,
|
||||
R_AARCH64_LDST16_ABS_LO12_NC = 284,
|
||||
R_AARCH64_LDST32_ABS_LO12_NC = 285,
|
||||
R_AARCH64_LDST64_ABS_LO12_NC = 286,
|
||||
R_AARCH64_MOVW_PREL_G0 = 287,
|
||||
R_AARCH64_MOVW_PREL_G0_NC = 288,
|
||||
R_AARCH64_MOVW_PREL_G1 = 289,
|
||||
R_AARCH64_MOVW_PREL_G1_NC = 290,
|
||||
R_AARCH64_MOVW_PREL_G2 = 291,
|
||||
R_AARCH64_MOVW_PREL_G2_NC = 292,
|
||||
R_AARCH64_MOVW_PREL_G3 = 293,
|
||||
R_AARCH64_LDST128_ABS_LO12_NC = 299,
|
||||
R_AARCH64_MOVW_GOTOFF_G0 = 300,
|
||||
R_AARCH64_MOVW_GOTOFF_G0_NC = 301,
|
||||
R_AARCH64_MOVW_GOTOFF_G1 = 302,
|
||||
R_AARCH64_MOVW_GOTOFF_G1_NC = 303,
|
||||
R_AARCH64_MOVW_GOTOFF_G2 = 304,
|
||||
R_AARCH64_MOVW_GOTOFF_G2_NC = 305,
|
||||
R_AARCH64_MOVW_GOTOFF_G3 = 306,
|
||||
R_AARCH64_GOTREL64 = 307,
|
||||
R_AARCH64_GOTREL32 = 308,
|
||||
R_AARCH64_GOT_LD_PREL19 = 309,
|
||||
R_AARCH64_LD64_GOTOFF_LO15 = 310,
|
||||
R_AARCH64_ADR_GOT_PAGE = 311,
|
||||
R_AARCH64_LD64_GOT_LO12_NC = 312,
|
||||
R_AARCH64_LD64_GOTPAGE_LO15 = 313,
|
||||
R_AARCH64_TLSGD_ADR_PREL21 = 512,
|
||||
R_AARCH64_TLSGD_ADR_PAGE21 = 513,
|
||||
R_AARCH64_TLSGD_ADD_LO12_NC = 514,
|
||||
R_AARCH64_TLSGD_MOVW_G1 = 515,
|
||||
R_AARCH64_TLSGD_MOVW_G0_NC = 516,
|
||||
R_AARCH64_TLSLD_ADR_PREL21 = 517,
|
||||
R_AARCH64_TLSLD_ADR_PAGE21 = 518,
|
||||
R_AARCH64_TLSLD_ADD_LO12_NC = 519,
|
||||
R_AARCH64_TLSLD_MOVW_G1 = 520,
|
||||
R_AARCH64_TLSLD_MOVW_G0_NC = 521,
|
||||
R_AARCH64_TLSLD_LD_PREL19 = 522,
|
||||
R_AARCH64_TLSLD_MOVW_DTPREL_G2 = 523,
|
||||
R_AARCH64_TLSLD_MOVW_DTPREL_G1 = 524,
|
||||
R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC = 525,
|
||||
R_AARCH64_TLSLD_MOVW_DTPREL_G0 = 526,
|
||||
R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC = 527,
|
||||
R_AARCH64_TLSLD_ADD_DTPREL_HI12 = 528,
|
||||
R_AARCH64_TLSLD_ADD_DTPREL_LO12 = 529,
|
||||
R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC = 530,
|
||||
R_AARCH64_TLSLD_LDST8_DTPREL_LO12 = 531,
|
||||
R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC = 532,
|
||||
R_AARCH64_TLSLD_LDST16_DTPREL_LO12 = 533,
|
||||
R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC = 534,
|
||||
R_AARCH64_TLSLD_LDST32_DTPREL_LO12 = 535,
|
||||
R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC = 536,
|
||||
R_AARCH64_TLSLD_LDST64_DTPREL_LO12 = 537,
|
||||
R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC = 538,
|
||||
R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 = 539,
|
||||
R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC = 540,
|
||||
R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 = 541,
|
||||
R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC = 542,
|
||||
R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 = 543,
|
||||
R_AARCH64_TLSLE_MOVW_TPREL_G2 = 544,
|
||||
R_AARCH64_TLSLE_MOVW_TPREL_G1 = 545,
|
||||
R_AARCH64_TLSLE_MOVW_TPREL_G1_NC = 546,
|
||||
R_AARCH64_TLSLE_MOVW_TPREL_G0 = 547,
|
||||
R_AARCH64_TLSLE_MOVW_TPREL_G0_NC = 548,
|
||||
R_AARCH64_TLSLE_ADD_TPREL_HI12 = 549,
|
||||
R_AARCH64_TLSLE_ADD_TPREL_LO12 = 550,
|
||||
R_AARCH64_TLSLE_ADD_TPREL_LO12_NC = 551,
|
||||
R_AARCH64_TLSLE_LDST8_TPREL_LO12 = 552,
|
||||
R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC = 553,
|
||||
R_AARCH64_TLSLE_LDST16_TPREL_LO12 = 554,
|
||||
R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC = 555,
|
||||
R_AARCH64_TLSLE_LDST32_TPREL_LO12 = 556,
|
||||
R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC = 557,
|
||||
R_AARCH64_TLSLE_LDST64_TPREL_LO12 = 558,
|
||||
R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC = 559,
|
||||
R_AARCH64_TLSDESC_LD_PREL19 = 560,
|
||||
R_AARCH64_TLSDESC_ADR_PREL21 = 561,
|
||||
R_AARCH64_TLSDESC_ADR_PAGE21 = 562,
|
||||
R_AARCH64_TLSDESC_LD64_LO12 = 563,
|
||||
R_AARCH64_TLSDESC_ADD_LO12 = 564,
|
||||
R_AARCH64_TLSDESC_OFF_G1 = 565,
|
||||
R_AARCH64_TLSDESC_OFF_G0_NC = 566,
|
||||
R_AARCH64_TLSDESC_LDR = 567,
|
||||
R_AARCH64_TLSDESC_ADD = 568,
|
||||
R_AARCH64_TLSDESC_CALL = 569,
|
||||
R_AARCH64_TLSLE_LDST128_TPREL_LO12 = 570,
|
||||
R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC = 571,
|
||||
R_AARCH64_TLSLD_LDST128_DTPREL_LO12 = 572,
|
||||
R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC = 573,
|
||||
R_AARCH64_COPY = 1024,
|
||||
R_AARCH64_GLOB_DAT = 1025,
|
||||
R_AARCH64_JUMP_SLOT = 1026,
|
||||
R_AARCH64_RELATIVE = 1027,
|
||||
R_AARCH64_TLS_DTPMOD64 = 1028,
|
||||
R_AARCH64_TLS_DTPREL64 = 1029,
|
||||
R_AARCH64_TLS_TPREL64 = 1030,
|
||||
R_AARCH64_TLSDESC = 1031
|
||||
}
|
||||
}
|
40
Ryujinx.HLE/Loaders/ElfSym.cs
Normal file
40
Ryujinx.HLE/Loaders/ElfSym.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
struct ElfSym
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public ElfSymType Type { get; private set; }
|
||||
public ElfSymBinding Binding { get; private set; }
|
||||
public ElfSymVisibility Visibility { get; private set; }
|
||||
|
||||
public bool IsFuncOrObject =>
|
||||
Type == ElfSymType.STT_FUNC ||
|
||||
Type == ElfSymType.STT_OBJECT;
|
||||
|
||||
public bool IsGlobalOrWeak =>
|
||||
Binding == ElfSymBinding.STB_GLOBAL ||
|
||||
Binding == ElfSymBinding.STB_WEAK;
|
||||
|
||||
public int SHIdx { get; private set; }
|
||||
public long Value { get; private set; }
|
||||
public long Size { get; private set; }
|
||||
|
||||
public ElfSym(
|
||||
string Name,
|
||||
int Info,
|
||||
int Other,
|
||||
int SHIdx,
|
||||
long Value,
|
||||
long Size)
|
||||
{
|
||||
this.Name = Name;
|
||||
this.Type = (ElfSymType)(Info & 0xf);
|
||||
this.Binding = (ElfSymBinding)(Info >> 4);
|
||||
this.Visibility = (ElfSymVisibility)Other;
|
||||
this.SHIdx = SHIdx;
|
||||
this.Value = Value;
|
||||
this.Size = Size;
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/Loaders/ElfSymBinding.cs
Normal file
9
Ryujinx.HLE/Loaders/ElfSymBinding.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
enum ElfSymBinding
|
||||
{
|
||||
STB_LOCAL = 0,
|
||||
STB_GLOBAL = 1,
|
||||
STB_WEAK = 2
|
||||
}
|
||||
}
|
13
Ryujinx.HLE/Loaders/ElfSymType.cs
Normal file
13
Ryujinx.HLE/Loaders/ElfSymType.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
enum ElfSymType
|
||||
{
|
||||
STT_NOTYPE = 0,
|
||||
STT_OBJECT = 1,
|
||||
STT_FUNC = 2,
|
||||
STT_SECTION = 3,
|
||||
STT_FILE = 4,
|
||||
STT_COMMON = 5,
|
||||
STT_TLS = 6
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/Loaders/ElfSymVisibility.cs
Normal file
10
Ryujinx.HLE/Loaders/ElfSymVisibility.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
enum ElfSymVisibility
|
||||
{
|
||||
STV_DEFAULT = 0,
|
||||
STV_INTERNAL = 1,
|
||||
STV_HIDDEN = 2,
|
||||
STV_PROTECTED = 3
|
||||
}
|
||||
}
|
173
Ryujinx.HLE/Loaders/Executable.cs
Normal file
173
Ryujinx.HLE/Loaders/Executable.cs
Normal file
|
@ -0,0 +1,173 @@
|
|||
using ChocolArm64.Memory;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.OsHle;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders
|
||||
{
|
||||
class Executable
|
||||
{
|
||||
private List<ElfDyn> Dynamic;
|
||||
|
||||
private Dictionary<long, string> m_SymbolTable;
|
||||
|
||||
public IReadOnlyDictionary<long, string> SymbolTable => m_SymbolTable;
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
private AMemory Memory;
|
||||
|
||||
public long ImageBase { get; private set; }
|
||||
public long ImageEnd { get; private set; }
|
||||
|
||||
public Executable(IExecutable Exe, AMemory Memory, long ImageBase)
|
||||
{
|
||||
Dynamic = new List<ElfDyn>();
|
||||
|
||||
m_SymbolTable = new Dictionary<long, string>();
|
||||
|
||||
Name = Exe.Name;
|
||||
|
||||
this.Memory = Memory;
|
||||
this.ImageBase = ImageBase;
|
||||
this.ImageEnd = ImageBase;
|
||||
|
||||
WriteData(ImageBase + Exe.TextOffset, Exe.Text, MemoryType.CodeStatic, AMemoryPerm.RX);
|
||||
WriteData(ImageBase + Exe.ROOffset, Exe.RO, MemoryType.CodeMutable, AMemoryPerm.Read);
|
||||
WriteData(ImageBase + Exe.DataOffset, Exe.Data, MemoryType.CodeMutable, AMemoryPerm.RW);
|
||||
|
||||
if (Exe.Mod0Offset == 0)
|
||||
{
|
||||
int BssOffset = Exe.DataOffset + Exe.Data.Length;
|
||||
int BssSize = Exe.BssSize;
|
||||
|
||||
MapBss(ImageBase + BssOffset, BssSize);
|
||||
|
||||
ImageEnd = ImageBase + BssOffset + BssSize;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
long Mod0Offset = ImageBase + Exe.Mod0Offset;
|
||||
|
||||
int Mod0Magic = Memory.ReadInt32(Mod0Offset + 0x0);
|
||||
long DynamicOffset = Memory.ReadInt32(Mod0Offset + 0x4) + Mod0Offset;
|
||||
long BssStartOffset = Memory.ReadInt32(Mod0Offset + 0x8) + Mod0Offset;
|
||||
long BssEndOffset = Memory.ReadInt32(Mod0Offset + 0xc) + Mod0Offset;
|
||||
long EhHdrStartOffset = Memory.ReadInt32(Mod0Offset + 0x10) + Mod0Offset;
|
||||
long EhHdrEndOffset = Memory.ReadInt32(Mod0Offset + 0x14) + Mod0Offset;
|
||||
long ModObjOffset = Memory.ReadInt32(Mod0Offset + 0x18) + Mod0Offset;
|
||||
|
||||
MapBss(BssStartOffset, BssEndOffset - BssStartOffset);
|
||||
|
||||
ImageEnd = BssEndOffset;
|
||||
|
||||
while (true)
|
||||
{
|
||||
long TagVal = Memory.ReadInt64(DynamicOffset + 0);
|
||||
long Value = Memory.ReadInt64(DynamicOffset + 8);
|
||||
|
||||
DynamicOffset += 0x10;
|
||||
|
||||
ElfDynTag Tag = (ElfDynTag)TagVal;
|
||||
|
||||
if (Tag == ElfDynTag.DT_NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Dynamic.Add(new ElfDyn(Tag, Value));
|
||||
}
|
||||
|
||||
long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB);
|
||||
long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB);
|
||||
|
||||
long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT);
|
||||
|
||||
while ((ulong)SymTblAddr < (ulong)StrTblAddr)
|
||||
{
|
||||
ElfSym Sym = GetSymbol(SymTblAddr, StrTblAddr);
|
||||
|
||||
m_SymbolTable.TryAdd(Sym.Value, Sym.Name);
|
||||
|
||||
SymTblAddr += SymEntSize;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteData(
|
||||
long Position,
|
||||
byte[] Data,
|
||||
MemoryType Type,
|
||||
AMemoryPerm Perm)
|
||||
{
|
||||
Memory.Manager.Map(Position, Data.Length, (int)Type, AMemoryPerm.Write);
|
||||
|
||||
Memory.WriteBytes(Position, Data);
|
||||
|
||||
Memory.Manager.Reprotect(Position, Data.Length, Perm);
|
||||
}
|
||||
|
||||
private void MapBss(long Position, long Size)
|
||||
{
|
||||
Memory.Manager.Map(Position, Size, (int)MemoryType.Normal, AMemoryPerm.RW);
|
||||
}
|
||||
|
||||
private ElfRel GetRelocation(long Position)
|
||||
{
|
||||
long Offset = Memory.ReadInt64(Position + 0);
|
||||
long Info = Memory.ReadInt64(Position + 8);
|
||||
long Addend = Memory.ReadInt64(Position + 16);
|
||||
|
||||
int RelType = (int)(Info >> 0);
|
||||
int SymIdx = (int)(Info >> 32);
|
||||
|
||||
ElfSym Symbol = GetSymbol(SymIdx);
|
||||
|
||||
return new ElfRel(Offset, Addend, Symbol, (ElfRelType)RelType);
|
||||
}
|
||||
|
||||
private ElfSym GetSymbol(int Index)
|
||||
{
|
||||
long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB);
|
||||
long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB);
|
||||
|
||||
long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT);
|
||||
|
||||
long Position = SymTblAddr + Index * SymEntSize;
|
||||
|
||||
return GetSymbol(Position, StrTblAddr);
|
||||
}
|
||||
|
||||
private ElfSym GetSymbol(long Position, long StrTblAddr)
|
||||
{
|
||||
int NameIndex = Memory.ReadInt32(Position + 0);
|
||||
int Info = Memory.ReadByte(Position + 4);
|
||||
int Other = Memory.ReadByte(Position + 5);
|
||||
int SHIdx = Memory.ReadInt16(Position + 6);
|
||||
long Value = Memory.ReadInt64(Position + 8);
|
||||
long Size = Memory.ReadInt64(Position + 16);
|
||||
|
||||
string Name = string.Empty;
|
||||
|
||||
for (int Chr; (Chr = Memory.ReadByte(StrTblAddr + NameIndex++)) != 0;)
|
||||
{
|
||||
Name += (char)Chr;
|
||||
}
|
||||
|
||||
return new ElfSym(Name, Info, Other, SHIdx, Value, Size);
|
||||
}
|
||||
|
||||
private long GetFirstValue(ElfDynTag Tag)
|
||||
{
|
||||
foreach (ElfDyn Entry in Dynamic)
|
||||
{
|
||||
if (Entry.Tag == Tag)
|
||||
{
|
||||
return Entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
17
Ryujinx.HLE/Loaders/Executables/IExecutable.cs
Normal file
17
Ryujinx.HLE/Loaders/Executables/IExecutable.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace Ryujinx.HLE.Loaders.Executables
|
||||
{
|
||||
public interface IExecutable
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
byte[] Text { get; }
|
||||
byte[] RO { get; }
|
||||
byte[] Data { get; }
|
||||
|
||||
int Mod0Offset { get; }
|
||||
int TextOffset { get; }
|
||||
int ROOffset { get; }
|
||||
int DataOffset { get; }
|
||||
int BssSize { get; }
|
||||
}
|
||||
}
|
60
Ryujinx.HLE/Loaders/Executables/Nro.cs
Normal file
60
Ryujinx.HLE/Loaders/Executables/Nro.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Executables
|
||||
{
|
||||
class Nro : IExecutable
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public byte[] Text { get; private set; }
|
||||
public byte[] RO { get; private set; }
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
public int Mod0Offset { get; private set; }
|
||||
public int TextOffset { get; private set; }
|
||||
public int ROOffset { get; private set; }
|
||||
public int DataOffset { get; private set; }
|
||||
public int BssSize { get; private set; }
|
||||
|
||||
public Nro(Stream Input, string Name)
|
||||
{
|
||||
this.Name = Name;
|
||||
|
||||
BinaryReader Reader = new BinaryReader(Input);
|
||||
|
||||
Input.Seek(4, SeekOrigin.Begin);
|
||||
|
||||
int Mod0Offset = Reader.ReadInt32();
|
||||
int Padding8 = Reader.ReadInt32();
|
||||
int Paddingc = Reader.ReadInt32();
|
||||
int NroMagic = Reader.ReadInt32();
|
||||
int Unknown14 = Reader.ReadInt32();
|
||||
int FileSize = Reader.ReadInt32();
|
||||
int Unknown1c = Reader.ReadInt32();
|
||||
int TextOffset = Reader.ReadInt32();
|
||||
int TextSize = Reader.ReadInt32();
|
||||
int ROOffset = Reader.ReadInt32();
|
||||
int ROSize = Reader.ReadInt32();
|
||||
int DataOffset = Reader.ReadInt32();
|
||||
int DataSize = Reader.ReadInt32();
|
||||
int BssSize = Reader.ReadInt32();
|
||||
|
||||
this.Mod0Offset = Mod0Offset;
|
||||
this.TextOffset = TextOffset;
|
||||
this.ROOffset = ROOffset;
|
||||
this.DataOffset = DataOffset;
|
||||
this.BssSize = BssSize;
|
||||
|
||||
byte[] Read(long Position, int Size)
|
||||
{
|
||||
Input.Seek(Position, SeekOrigin.Begin);
|
||||
|
||||
return Reader.ReadBytes(Size);
|
||||
}
|
||||
|
||||
Text = Read(TextOffset, TextSize);
|
||||
RO = Read(ROOffset, ROSize);
|
||||
Data = Read(DataOffset, DataSize);
|
||||
}
|
||||
}
|
||||
}
|
121
Ryujinx.HLE/Loaders/Executables/Nso.cs
Normal file
121
Ryujinx.HLE/Loaders/Executables/Nso.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
using Ryujinx.HLE.Loaders.Compression;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Executables
|
||||
{
|
||||
class Nso : IExecutable
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public byte[] Text { get; private set; }
|
||||
public byte[] RO { get; private set; }
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
public int Mod0Offset { get; private set; }
|
||||
public int TextOffset { get; private set; }
|
||||
public int ROOffset { get; private set; }
|
||||
public int DataOffset { get; private set; }
|
||||
public int BssSize { get; private set; }
|
||||
|
||||
[Flags]
|
||||
private enum NsoFlags
|
||||
{
|
||||
IsTextCompressed = 1 << 0,
|
||||
IsROCompressed = 1 << 1,
|
||||
IsDataCompressed = 1 << 2,
|
||||
HasTextHash = 1 << 3,
|
||||
HasROHash = 1 << 4,
|
||||
HasDataHash = 1 << 5
|
||||
}
|
||||
|
||||
public Nso(Stream Input, string Name)
|
||||
{
|
||||
this.Name = Name;
|
||||
|
||||
BinaryReader Reader = new BinaryReader(Input);
|
||||
|
||||
Input.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
int NsoMagic = Reader.ReadInt32();
|
||||
int Version = Reader.ReadInt32();
|
||||
int Reserved = Reader.ReadInt32();
|
||||
int FlagsMsk = Reader.ReadInt32();
|
||||
int TextOffset = Reader.ReadInt32();
|
||||
int TextMemOffset = Reader.ReadInt32();
|
||||
int TextDecSize = Reader.ReadInt32();
|
||||
int ModNameOffset = Reader.ReadInt32();
|
||||
int ROOffset = Reader.ReadInt32();
|
||||
int ROMemOffset = Reader.ReadInt32();
|
||||
int RODecSize = Reader.ReadInt32();
|
||||
int ModNameSize = Reader.ReadInt32();
|
||||
int DataOffset = Reader.ReadInt32();
|
||||
int DataMemOffset = Reader.ReadInt32();
|
||||
int DataDecSize = Reader.ReadInt32();
|
||||
int BssSize = Reader.ReadInt32();
|
||||
|
||||
byte[] BuildId = Reader.ReadBytes(0x20);
|
||||
|
||||
int TextSize = Reader.ReadInt32();
|
||||
int ROSize = Reader.ReadInt32();
|
||||
int DataSize = Reader.ReadInt32();
|
||||
|
||||
Input.Seek(0x24, SeekOrigin.Current);
|
||||
|
||||
int DynStrOffset = Reader.ReadInt32();
|
||||
int DynStrSize = Reader.ReadInt32();
|
||||
int DynSymOffset = Reader.ReadInt32();
|
||||
int DynSymSize = Reader.ReadInt32();
|
||||
|
||||
byte[] TextHash = Reader.ReadBytes(0x20);
|
||||
byte[] ROHash = Reader.ReadBytes(0x20);
|
||||
byte[] DataHash = Reader.ReadBytes(0x20);
|
||||
|
||||
NsoFlags Flags = (NsoFlags)FlagsMsk;
|
||||
|
||||
this.TextOffset = TextMemOffset;
|
||||
this.ROOffset = ROMemOffset;
|
||||
this.DataOffset = DataMemOffset;
|
||||
this.BssSize = BssSize;
|
||||
|
||||
//Text segment
|
||||
Input.Seek(TextOffset, SeekOrigin.Begin);
|
||||
|
||||
Text = Reader.ReadBytes(TextSize);
|
||||
|
||||
if (Flags.HasFlag(NsoFlags.IsTextCompressed) || true)
|
||||
{
|
||||
Text = Lz4.Decompress(Text, TextDecSize);
|
||||
}
|
||||
|
||||
//Read-only data segment
|
||||
Input.Seek(ROOffset, SeekOrigin.Begin);
|
||||
|
||||
RO = Reader.ReadBytes(ROSize);
|
||||
|
||||
if (Flags.HasFlag(NsoFlags.IsROCompressed) || true)
|
||||
{
|
||||
RO = Lz4.Decompress(RO, RODecSize);
|
||||
}
|
||||
|
||||
//Data segment
|
||||
Input.Seek(DataOffset, SeekOrigin.Begin);
|
||||
|
||||
Data = Reader.ReadBytes(DataSize);
|
||||
|
||||
if (Flags.HasFlag(NsoFlags.IsDataCompressed) || true)
|
||||
{
|
||||
Data = Lz4.Decompress(Data, DataDecSize);
|
||||
}
|
||||
|
||||
using (MemoryStream TextMS = new MemoryStream(Text))
|
||||
{
|
||||
BinaryReader TextReader = new BinaryReader(TextMS);
|
||||
|
||||
TextMS.Seek(4, SeekOrigin.Begin);
|
||||
|
||||
Mod0Offset = TextReader.ReadInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
Ryujinx.HLE/Logging/LogClass.cs
Normal file
41
Ryujinx.HLE/Logging/LogClass.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
namespace Ryujinx.HLE.Logging
|
||||
{
|
||||
public enum LogClass
|
||||
{
|
||||
Audio,
|
||||
Cpu,
|
||||
Gpu,
|
||||
Hid,
|
||||
Kernel,
|
||||
KernelIpc,
|
||||
KernelScheduler,
|
||||
KernelSvc,
|
||||
Loader,
|
||||
Service,
|
||||
ServiceAcc,
|
||||
ServiceAm,
|
||||
ServiceApm,
|
||||
ServiceAudio,
|
||||
ServiceBsd,
|
||||
ServiceCaps,
|
||||
ServiceFriend,
|
||||
ServiceFs,
|
||||
ServiceHid,
|
||||
ServiceLm,
|
||||
ServiceMm,
|
||||
ServiceNfp,
|
||||
ServiceNifm,
|
||||
ServiceNs,
|
||||
ServiceNv,
|
||||
ServicePctl,
|
||||
ServicePl,
|
||||
ServicePrepo,
|
||||
ServiceSet,
|
||||
ServiceSfdnsres,
|
||||
ServiceSm,
|
||||
ServiceSsl,
|
||||
ServiceSss,
|
||||
ServiceTime,
|
||||
ServiceVi
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/Logging/LogEventArgs.cs
Normal file
19
Ryujinx.HLE/Logging/LogEventArgs.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.Logging
|
||||
{
|
||||
public class LogEventArgs : EventArgs
|
||||
{
|
||||
public LogLevel Level { get; private set; }
|
||||
public TimeSpan Time { get; private set; }
|
||||
|
||||
public string Message { get; private set; }
|
||||
|
||||
public LogEventArgs(LogLevel Level, TimeSpan Time, string Message)
|
||||
{
|
||||
this.Level = Level;
|
||||
this.Time = Time;
|
||||
this.Message = Message;
|
||||
}
|
||||
}
|
||||
}
|
11
Ryujinx.HLE/Logging/LogLevel.cs
Normal file
11
Ryujinx.HLE/Logging/LogLevel.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.HLE.Logging
|
||||
{
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Stub,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
}
|
84
Ryujinx.HLE/Logging/Logger.cs
Normal file
84
Ryujinx.HLE/Logging/Logger.cs
Normal file
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.HLE.Logging
|
||||
{
|
||||
public class Logger
|
||||
{
|
||||
private bool[] EnabledLevels;
|
||||
private bool[] EnabledClasses;
|
||||
|
||||
public event EventHandler<LogEventArgs> Updated;
|
||||
|
||||
private Stopwatch Time;
|
||||
|
||||
public Logger()
|
||||
{
|
||||
EnabledLevels = new bool[Enum.GetNames(typeof(LogLevel)).Length];
|
||||
EnabledClasses = new bool[Enum.GetNames(typeof(LogClass)).Length];
|
||||
|
||||
EnabledLevels[(int)LogLevel.Stub] = true;
|
||||
EnabledLevels[(int)LogLevel.Info] = true;
|
||||
EnabledLevels[(int)LogLevel.Warning] = true;
|
||||
EnabledLevels[(int)LogLevel.Error] = true;
|
||||
|
||||
for (int Index = 0; Index < EnabledClasses.Length; Index++)
|
||||
{
|
||||
EnabledClasses[Index] = true;
|
||||
}
|
||||
|
||||
Time = new Stopwatch();
|
||||
|
||||
Time.Start();
|
||||
}
|
||||
|
||||
public void SetEnable(LogLevel Level, bool Enabled)
|
||||
{
|
||||
EnabledLevels[(int)Level] = Enabled;
|
||||
}
|
||||
|
||||
public void SetEnable(LogClass Class, bool Enabled)
|
||||
{
|
||||
EnabledClasses[(int)Class] = Enabled;
|
||||
}
|
||||
|
||||
internal void PrintDebug(LogClass Class, string Message, [CallerMemberName] string Caller = "")
|
||||
{
|
||||
Print(LogLevel.Debug, Class, GetFormattedMessage(Class, Message, Caller));
|
||||
}
|
||||
|
||||
internal void PrintStub(LogClass Class, string Message, [CallerMemberName] string Caller = "")
|
||||
{
|
||||
Print(LogLevel.Stub, Class, GetFormattedMessage(Class, Message, Caller));
|
||||
}
|
||||
|
||||
internal void PrintInfo(LogClass Class, string Message, [CallerMemberName] string Caller = "")
|
||||
{
|
||||
Print(LogLevel.Info, Class, GetFormattedMessage(Class, Message, Caller));
|
||||
}
|
||||
|
||||
internal void PrintWarning(LogClass Class, string Message, [CallerMemberName] string Caller = "")
|
||||
{
|
||||
Print(LogLevel.Warning, Class, GetFormattedMessage(Class, Message, Caller));
|
||||
}
|
||||
|
||||
internal void PrintError(LogClass Class, string Message, [CallerMemberName] string Caller = "")
|
||||
{
|
||||
Print(LogLevel.Error, Class, GetFormattedMessage(Class, Message, Caller));
|
||||
}
|
||||
|
||||
private void Print(LogLevel Level, LogClass Class, string Message)
|
||||
{
|
||||
if (EnabledLevels[(int)Level] && EnabledClasses[(int)Class])
|
||||
{
|
||||
Updated?.Invoke(this, new LogEventArgs(Level, Time.Elapsed, Message));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFormattedMessage(LogClass Class, string Message, string Caller)
|
||||
{
|
||||
return $"{Class} {Caller}: {Message}";
|
||||
}
|
||||
}
|
||||
}
|
62
Ryujinx.HLE/OsHle/AppletStateMgr.cs
Normal file
62
Ryujinx.HLE/OsHle/AppletStateMgr.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using Ryujinx.HLE.OsHle.Services.Am;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
class AppletStateMgr : IDisposable
|
||||
{
|
||||
private ConcurrentQueue<MessageInfo> Messages;
|
||||
|
||||
public FocusState FocusState { get; private set; }
|
||||
|
||||
public KEvent MessageEvent { get; private set; }
|
||||
|
||||
public AppletStateMgr()
|
||||
{
|
||||
Messages = new ConcurrentQueue<MessageInfo>();
|
||||
|
||||
MessageEvent = new KEvent();
|
||||
}
|
||||
|
||||
public void SetFocus(bool IsFocused)
|
||||
{
|
||||
FocusState = IsFocused
|
||||
? FocusState.InFocus
|
||||
: FocusState.OutOfFocus;
|
||||
|
||||
EnqueueMessage(MessageInfo.FocusStateChanged);
|
||||
}
|
||||
|
||||
public void EnqueueMessage(MessageInfo Message)
|
||||
{
|
||||
Messages.Enqueue(Message);
|
||||
|
||||
MessageEvent.WaitEvent.Set();
|
||||
}
|
||||
|
||||
public bool TryDequeueMessage(out MessageInfo Message)
|
||||
{
|
||||
if (Messages.Count < 2)
|
||||
{
|
||||
MessageEvent.WaitEvent.Reset();
|
||||
}
|
||||
|
||||
return Messages.TryDequeue(out Message);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
MessageEvent.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
416
Ryujinx.HLE/OsHle/Diagnostics/Demangler.cs
Normal file
416
Ryujinx.HLE/OsHle/Diagnostics/Demangler.cs
Normal file
|
@ -0,0 +1,416 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Diagnostics
|
||||
{
|
||||
static class Demangler
|
||||
{
|
||||
private static readonly Dictionary<string, string> BuiltinTypes = new Dictionary<string, string>
|
||||
{
|
||||
{ "v", "void" },
|
||||
{ "w", "wchar_t" },
|
||||
{ "b", "bool" },
|
||||
{ "c", "char" },
|
||||
{ "a", "signed char" },
|
||||
{ "h", "unsigned char" },
|
||||
{ "s", "short" },
|
||||
{ "t", "unsigned short" },
|
||||
{ "i", "int" },
|
||||
{ "j", "unsigned int" },
|
||||
{ "l", "long" },
|
||||
{ "m", "unsigned long" },
|
||||
{ "x", "long long" },
|
||||
{ "y", "unsigned long long" },
|
||||
{ "n", "__int128" },
|
||||
{ "o", "unsigned __int128" },
|
||||
{ "f", "float" },
|
||||
{ "d", "double" },
|
||||
{ "e", "long double" },
|
||||
{ "g", "__float128" },
|
||||
{ "z", "..." },
|
||||
{ "Dd", "__iec559_double" },
|
||||
{ "De", "__iec559_float128" },
|
||||
{ "Df", "__iec559_float" },
|
||||
{ "Dh", "__iec559_float16" },
|
||||
{ "Di", "char32_t" },
|
||||
{ "Ds", "char16_t" },
|
||||
{ "Da", "decltype(auto)" },
|
||||
{ "Dn", "std::nullptr_t" },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> SubstitutionExtra = new Dictionary<string, string>
|
||||
{
|
||||
{"Sa", "std::allocator"},
|
||||
{"Sb", "std::basic_string"},
|
||||
{"Ss", "std::basic_string<char, ::std::char_traits<char>, ::std::allocator<char>>"},
|
||||
{"Si", "std::basic_istream<char, ::std::char_traits<char>>"},
|
||||
{"So", "std::basic_ostream<char, ::std::char_traits<char>>"},
|
||||
{"Sd", "std::basic_iostream<char, ::std::char_traits<char>>"}
|
||||
};
|
||||
|
||||
private static int FromBase36(string encoded)
|
||||
{
|
||||
string base36 = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
char[] reversedEncoded = encoded.ToLower().ToCharArray().Reverse().ToArray();
|
||||
int result = 0;
|
||||
for (int i = 0; i < reversedEncoded.Length; i++)
|
||||
{
|
||||
char c = reversedEncoded[i];
|
||||
int value = base36.IndexOf(c);
|
||||
if (value == -1)
|
||||
return -1;
|
||||
result += value * (int)Math.Pow(36, i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetCompressedValue(string compression, List<string> compressionData, out int pos)
|
||||
{
|
||||
string res = null;
|
||||
bool canHaveUnqualifiedName = false;
|
||||
pos = -1;
|
||||
if (compressionData.Count == 0 || !compression.StartsWith("S"))
|
||||
return null;
|
||||
|
||||
if (compression.Length >= 2 && SubstitutionExtra.TryGetValue(compression.Substring(0, 2), out string substitutionValue))
|
||||
{
|
||||
pos = 1;
|
||||
res = substitutionValue;
|
||||
compression = compression.Substring(2);
|
||||
}
|
||||
else if (compression.StartsWith("St"))
|
||||
{
|
||||
pos = 1;
|
||||
canHaveUnqualifiedName = true;
|
||||
res = "std";
|
||||
compression = compression.Substring(2);
|
||||
}
|
||||
else if (compression.StartsWith("S_"))
|
||||
{
|
||||
pos = 1;
|
||||
res = compressionData[0];
|
||||
canHaveUnqualifiedName = true;
|
||||
compression = compression.Substring(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
int id = -1;
|
||||
int underscorePos = compression.IndexOf('_');
|
||||
if (underscorePos == -1)
|
||||
return null;
|
||||
string partialId = compression.Substring(1, underscorePos - 1);
|
||||
|
||||
id = FromBase36(partialId);
|
||||
if (id == -1 || compressionData.Count <= (id + 1))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
res = compressionData[id + 1];
|
||||
pos = partialId.Length + 1;
|
||||
canHaveUnqualifiedName= true;
|
||||
compression = compression.Substring(pos);
|
||||
}
|
||||
if (res != null)
|
||||
{
|
||||
if (canHaveUnqualifiedName)
|
||||
{
|
||||
List<string> type = ReadName(compression, compressionData, out int endOfNameType);
|
||||
if (endOfNameType != -1 && type != null)
|
||||
{
|
||||
pos += endOfNameType;
|
||||
res = res + "::" + type[type.Count - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static List<string> ReadName(string mangled, List<string> compressionData, out int pos, bool isNested = true)
|
||||
{
|
||||
List<string> res = new List<string>();
|
||||
string charCountString = null;
|
||||
int charCount = 0;
|
||||
int i;
|
||||
|
||||
pos = -1;
|
||||
for (i = 0; i < mangled.Length; i++)
|
||||
{
|
||||
char chr = mangled[i];
|
||||
if (charCountString == null)
|
||||
{
|
||||
if (ReadCVQualifiers(chr) != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (chr == 'S')
|
||||
{
|
||||
string data = GetCompressedValue(mangled.Substring(i), compressionData, out pos);
|
||||
if (pos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (res.Count == 0)
|
||||
res.Add(data);
|
||||
else
|
||||
res.Add(res[res.Count - 1] + "::" + data);
|
||||
i += pos;
|
||||
if (i < mangled.Length && mangled[i] == 'E')
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (chr == 'E')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Char.IsDigit(chr))
|
||||
{
|
||||
charCountString += chr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!int.TryParse(charCountString, out charCount))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string demangledPart = mangled.Substring(i, charCount);
|
||||
if (res.Count == 0)
|
||||
res.Add(demangledPart);
|
||||
else
|
||||
res.Add(res[res.Count - 1] + "::" + demangledPart);
|
||||
i = i + charCount - 1;
|
||||
charCount = 0;
|
||||
charCountString = null;
|
||||
if (!isNested)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (res.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
pos = i;
|
||||
return res;
|
||||
}
|
||||
|
||||
private static string ReadBuiltinType(string mangledType, out int pos)
|
||||
{
|
||||
string res = null;
|
||||
string possibleBuiltinType;
|
||||
pos = -1;
|
||||
possibleBuiltinType = mangledType[0].ToString();
|
||||
if (!BuiltinTypes.TryGetValue(possibleBuiltinType, out res))
|
||||
{
|
||||
if (mangledType.Length >= 2)
|
||||
{
|
||||
// Try to match the first 2 chars if the first call failed
|
||||
possibleBuiltinType = mangledType.Substring(0, 2);
|
||||
BuiltinTypes.TryGetValue(possibleBuiltinType, out res);
|
||||
}
|
||||
}
|
||||
if (res != null)
|
||||
pos = possibleBuiltinType.Length;
|
||||
return res;
|
||||
}
|
||||
|
||||
private static string ReadCVQualifiers(char qualifier)
|
||||
{
|
||||
if (qualifier == 'r')
|
||||
return "restricted";
|
||||
else if (qualifier == 'V')
|
||||
return "volatile";
|
||||
else if (qualifier == 'K')
|
||||
return "const";
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ReadRefQualifiers(char qualifier)
|
||||
{
|
||||
if (qualifier == 'R')
|
||||
return "&";
|
||||
else if (qualifier == 'O')
|
||||
return "&&";
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ReadSpecialQualifiers(char qualifier)
|
||||
{
|
||||
if (qualifier == 'P')
|
||||
return "*";
|
||||
else if (qualifier == 'C')
|
||||
return "complex";
|
||||
else if (qualifier == 'G')
|
||||
return "imaginary";
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<string> ReadParameters(string mangledParams, List<string> compressionData, out int pos)
|
||||
{
|
||||
List<string> res = new List<string>();
|
||||
List<string> refQualifiers = new List<string>();
|
||||
string parsedTypePart = null;
|
||||
string currentRefQualifiers = null;
|
||||
string currentBuiltinType = null;
|
||||
string currentSpecialQualifiers = null;
|
||||
string currentCompressedValue = null;
|
||||
int i = 0;
|
||||
pos = -1;
|
||||
|
||||
for (i = 0; i < mangledParams.Length; i++)
|
||||
{
|
||||
if (currentBuiltinType != null)
|
||||
{
|
||||
string currentCVQualifier = String.Join(" ", refQualifiers);
|
||||
// Try to mimic the compression indexing
|
||||
if (currentRefQualifiers != null)
|
||||
{
|
||||
compressionData.Add(currentBuiltinType + currentRefQualifiers);
|
||||
}
|
||||
if (refQualifiers.Count != 0)
|
||||
{
|
||||
compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers);
|
||||
}
|
||||
if (currentSpecialQualifiers != null)
|
||||
{
|
||||
compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers + currentSpecialQualifiers);
|
||||
}
|
||||
if (currentRefQualifiers == null && currentCVQualifier == null && currentSpecialQualifiers == null)
|
||||
{
|
||||
compressionData.Add(currentBuiltinType);
|
||||
}
|
||||
currentBuiltinType = null;
|
||||
currentCompressedValue = null;
|
||||
currentCVQualifier = null;
|
||||
currentRefQualifiers = null;
|
||||
refQualifiers.Clear();
|
||||
currentSpecialQualifiers = null;
|
||||
}
|
||||
char chr = mangledParams[i];
|
||||
string part = mangledParams.Substring(i);
|
||||
|
||||
// Try to read qualifiers
|
||||
parsedTypePart = ReadCVQualifiers(chr);
|
||||
if (parsedTypePart != null)
|
||||
{
|
||||
refQualifiers.Add(parsedTypePart);
|
||||
|
||||
// need more data
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedTypePart = ReadRefQualifiers(chr);
|
||||
if (parsedTypePart != null)
|
||||
{
|
||||
currentRefQualifiers = parsedTypePart;
|
||||
|
||||
// need more data
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedTypePart = ReadSpecialQualifiers(chr);
|
||||
if (parsedTypePart != null)
|
||||
{
|
||||
currentSpecialQualifiers = parsedTypePart;
|
||||
|
||||
// need more data
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: extended-qualifier?
|
||||
|
||||
if (part.StartsWith("S"))
|
||||
{
|
||||
parsedTypePart = GetCompressedValue(part, compressionData, out pos);
|
||||
if (pos != -1 && parsedTypePart != null)
|
||||
{
|
||||
currentCompressedValue = parsedTypePart;
|
||||
i += pos;
|
||||
res.Add(currentCompressedValue + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
|
||||
currentBuiltinType = null;
|
||||
currentCompressedValue = null;
|
||||
currentRefQualifiers = null;
|
||||
refQualifiers.Clear();
|
||||
currentSpecialQualifiers = null;
|
||||
continue;
|
||||
}
|
||||
pos = -1;
|
||||
return null;
|
||||
}
|
||||
else if (part.StartsWith("N"))
|
||||
{
|
||||
part = part.Substring(1);
|
||||
List<string> name = ReadName(part, compressionData, out pos);
|
||||
if (pos != -1 && name != null)
|
||||
{
|
||||
i += pos + 1;
|
||||
res.Add(name[name.Count - 1] + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
|
||||
currentBuiltinType = null;
|
||||
currentCompressedValue = null;
|
||||
currentRefQualifiers = null;
|
||||
refQualifiers.Clear();
|
||||
currentSpecialQualifiers = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Try builting
|
||||
parsedTypePart = ReadBuiltinType(part, out pos);
|
||||
if (pos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
currentBuiltinType = parsedTypePart;
|
||||
res.Add(currentBuiltinType + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
|
||||
i = i + pos -1;
|
||||
}
|
||||
pos = i;
|
||||
return res;
|
||||
}
|
||||
|
||||
private static string ParseFunctionName(string mangled)
|
||||
{
|
||||
List<string> compressionData = new List<string>();
|
||||
int pos = 0;
|
||||
string res;
|
||||
bool isNested = mangled.StartsWith("N");
|
||||
|
||||
// If it's start with "N" it must be a nested function name
|
||||
if (isNested)
|
||||
mangled = mangled.Substring(1);
|
||||
compressionData = ReadName(mangled, compressionData, out pos, isNested);
|
||||
if (pos == -1)
|
||||
return null;
|
||||
res = compressionData[compressionData.Count - 1];
|
||||
compressionData.Remove(res);
|
||||
mangled = mangled.Substring(pos + 1);
|
||||
|
||||
// more data? maybe not a data name so...
|
||||
if (mangled != String.Empty)
|
||||
{
|
||||
List<string> parameters = ReadParameters(mangled, compressionData, out pos);
|
||||
// parameters parsing error, we return the original data to avoid information loss.
|
||||
if (pos == -1)
|
||||
return null;
|
||||
parameters = parameters.Select(outer => outer.Trim()).ToList();
|
||||
res += "(" + String.Join(", ", parameters) + ")";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static string Parse(string originalMangled)
|
||||
{
|
||||
if (originalMangled.StartsWith("_Z"))
|
||||
{
|
||||
// We assume that we have a name (TOOD: support special names)
|
||||
string res = ParseFunctionName(originalMangled.Substring(2));
|
||||
if (res == null)
|
||||
return originalMangled;
|
||||
return res;
|
||||
}
|
||||
return originalMangled;
|
||||
}
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/OsHle/ErrorCode.cs
Normal file
10
Ryujinx.HLE/OsHle/ErrorCode.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
static class ErrorCode
|
||||
{
|
||||
public static uint MakeError(ErrorModule Module, int Code)
|
||||
{
|
||||
return (uint)Module | ((uint)Code << 9);
|
||||
}
|
||||
}
|
||||
}
|
101
Ryujinx.HLE/OsHle/ErrorModule.cs
Normal file
101
Ryujinx.HLE/OsHle/ErrorModule.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
enum ErrorModule
|
||||
{
|
||||
Kernel = 1,
|
||||
Fs = 2,
|
||||
Os = 3, // (Memory, Thread, Mutex, NVIDIA)
|
||||
Htcs = 4,
|
||||
Ncm = 5,
|
||||
Dd = 6,
|
||||
Debug_Monitor = 7,
|
||||
Lr = 8,
|
||||
Loader = 9,
|
||||
IPC_Command_Interface = 10,
|
||||
IPC = 11,
|
||||
Pm = 15,
|
||||
Ns = 16,
|
||||
Socket = 17,
|
||||
Htc = 18,
|
||||
Ncm_Content = 20,
|
||||
Sm = 21,
|
||||
RO_Userland = 22,
|
||||
SdMmc = 24,
|
||||
Ovln = 25,
|
||||
Spl = 26,
|
||||
Ethc = 100,
|
||||
I2C = 101,
|
||||
Gpio = 102,
|
||||
Uart = 103,
|
||||
Settings = 105,
|
||||
Wlan = 107,
|
||||
Xcd = 108,
|
||||
Nifm = 110,
|
||||
Hwopus = 111,
|
||||
Bluetooth = 113,
|
||||
Vi = 114,
|
||||
Nfp = 115,
|
||||
Time = 116,
|
||||
Fgm = 117,
|
||||
Oe = 118,
|
||||
Pcie = 120,
|
||||
Friends = 121,
|
||||
Bcat = 122,
|
||||
SSL = 123,
|
||||
Account = 124,
|
||||
News = 125,
|
||||
Mii = 126,
|
||||
Nfc = 127,
|
||||
Am = 128,
|
||||
Play_Report = 129,
|
||||
Ahid = 130,
|
||||
Qlaunch = 132,
|
||||
Pcv = 133,
|
||||
Omm = 134,
|
||||
Bpc = 135,
|
||||
Psm = 136,
|
||||
Nim = 137,
|
||||
Psc = 138,
|
||||
Tc = 139,
|
||||
Usb = 140,
|
||||
Nsd = 141,
|
||||
Pctl = 142,
|
||||
Btm = 143,
|
||||
Ec = 144,
|
||||
ETicket = 145,
|
||||
Ngc = 146,
|
||||
Error_Report = 147,
|
||||
Apm = 148,
|
||||
Profiler = 150,
|
||||
Error_Upload = 151,
|
||||
Audio = 153,
|
||||
Npns = 154,
|
||||
Npns_Http_Stream = 155,
|
||||
Arp = 157,
|
||||
Swkbd = 158,
|
||||
Boot = 159,
|
||||
Nfc_Mifare = 161,
|
||||
Userland_Assert = 162,
|
||||
Fatal = 163,
|
||||
Nim_Shop = 164,
|
||||
Spsm = 165,
|
||||
Bgtc = 167,
|
||||
Userland_Crash = 168,
|
||||
SRepo = 180,
|
||||
Dauth = 181,
|
||||
Hid = 202,
|
||||
Ldn = 203,
|
||||
Irsensor = 205,
|
||||
Capture = 206,
|
||||
Manu = 208,
|
||||
Atk = 209,
|
||||
Web = 210,
|
||||
Grc = 212,
|
||||
Migration = 216,
|
||||
Migration_Ldc_Server = 217,
|
||||
General_Web_Applet = 800,
|
||||
Wifi_Web_Auth_Applet = 809,
|
||||
Whitelisted_Applet = 810,
|
||||
ShopN = 811
|
||||
}
|
||||
}
|
11
Ryujinx.HLE/OsHle/Exceptions/GuestBrokeExecutionException.cs
Normal file
11
Ryujinx.HLE/OsHle/Exceptions/GuestBrokeExecutionException.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Exceptions
|
||||
{
|
||||
public class GuestBrokeExecutionException : Exception
|
||||
{
|
||||
private const string ExMsg = "The guest program broke execution!";
|
||||
|
||||
public GuestBrokeExecutionException() : base(ExMsg) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Exceptions
|
||||
{
|
||||
public class UndefinedInstructionException : Exception
|
||||
{
|
||||
private const string ExMsg = "The instruction at 0x{0:x16} (opcode 0x{1:x8}) is undefined!";
|
||||
|
||||
public UndefinedInstructionException() : base() { }
|
||||
|
||||
public UndefinedInstructionException(long Position, int OpCode) : base(string.Format(ExMsg, Position, OpCode)) { }
|
||||
}
|
||||
}
|
69
Ryujinx.HLE/OsHle/GlobalStateTable.cs
Normal file
69
Ryujinx.HLE/OsHle/GlobalStateTable.cs
Normal file
|
@ -0,0 +1,69 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
class GlobalStateTable
|
||||
{
|
||||
private ConcurrentDictionary<Process, IdDictionary> DictByProcess;
|
||||
|
||||
public GlobalStateTable()
|
||||
{
|
||||
DictByProcess = new ConcurrentDictionary<Process, IdDictionary>();
|
||||
}
|
||||
|
||||
public bool Add(Process Process, int Id, object Data)
|
||||
{
|
||||
IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary());
|
||||
|
||||
return Dict.Add(Id, Data);
|
||||
}
|
||||
|
||||
public int Add(Process Process, object Data)
|
||||
{
|
||||
IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary());
|
||||
|
||||
return Dict.Add(Data);
|
||||
}
|
||||
|
||||
public object GetData(Process Process, int Id)
|
||||
{
|
||||
if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
|
||||
{
|
||||
return Dict.GetData(Id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public T GetData<T>(Process Process, int Id)
|
||||
{
|
||||
if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
|
||||
{
|
||||
return Dict.GetData<T>(Id);
|
||||
}
|
||||
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public object Delete(Process Process, int Id)
|
||||
{
|
||||
if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
|
||||
{
|
||||
return Dict.Delete(Id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ICollection<object> DeleteProcess(Process Process)
|
||||
{
|
||||
if (DictByProcess.TryRemove(Process, out IdDictionary Dict))
|
||||
{
|
||||
return Dict.Clear();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
44
Ryujinx.HLE/OsHle/Handles/HSharedMem.cs
Normal file
44
Ryujinx.HLE/OsHle/Handles/HSharedMem.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using ChocolArm64.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class HSharedMem
|
||||
{
|
||||
private List<(AMemory, long)> Positions;
|
||||
|
||||
public EventHandler<EventArgs> MemoryMapped;
|
||||
public EventHandler<EventArgs> MemoryUnmapped;
|
||||
|
||||
public HSharedMem()
|
||||
{
|
||||
Positions = new List<(AMemory, long)>();
|
||||
}
|
||||
|
||||
public void AddVirtualPosition(AMemory Memory, long Position)
|
||||
{
|
||||
lock (Positions)
|
||||
{
|
||||
Positions.Add((Memory, Position));
|
||||
|
||||
MemoryMapped?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveVirtualPosition(AMemory Memory, long Position)
|
||||
{
|
||||
lock (Positions)
|
||||
{
|
||||
Positions.Remove((Memory, Position));
|
||||
|
||||
MemoryUnmapped?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public (AMemory, long)[] GetVirtualPositions()
|
||||
{
|
||||
return Positions.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
21
Ryujinx.HLE/OsHle/Handles/HTransferMem.cs
Normal file
21
Ryujinx.HLE/OsHle/Handles/HTransferMem.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using ChocolArm64.Memory;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class HTransferMem
|
||||
{
|
||||
public AMemory Memory { get; private set; }
|
||||
public AMemoryPerm Perm { get; private set; }
|
||||
|
||||
public long Position { get; private set; }
|
||||
public long Size { get; private set; }
|
||||
|
||||
public HTransferMem(AMemory Memory, AMemoryPerm Perm, long Position, long Size)
|
||||
{
|
||||
this.Memory = Memory;
|
||||
this.Perm = Perm;
|
||||
this.Position = Position;
|
||||
this.Size = Size;
|
||||
}
|
||||
}
|
||||
}
|
4
Ryujinx.HLE/OsHle/Handles/KEvent.cs
Normal file
4
Ryujinx.HLE/OsHle/Handles/KEvent.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class KEvent : KSynchronizationObject { }
|
||||
}
|
34
Ryujinx.HLE/OsHle/Handles/KProcessHandleTable.cs
Normal file
34
Ryujinx.HLE/OsHle/Handles/KProcessHandleTable.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class KProcessHandleTable
|
||||
{
|
||||
private IdDictionary Handles;
|
||||
|
||||
public KProcessHandleTable()
|
||||
{
|
||||
Handles = new IdDictionary();
|
||||
}
|
||||
|
||||
public int OpenHandle(object Obj)
|
||||
{
|
||||
return Handles.Add(Obj);
|
||||
}
|
||||
|
||||
public T GetData<T>(int Handle)
|
||||
{
|
||||
return Handles.GetData<T>(Handle);
|
||||
}
|
||||
|
||||
public object CloseHandle(int Handle)
|
||||
{
|
||||
return Handles.Delete(Handle);
|
||||
}
|
||||
|
||||
public ICollection<object> Clear()
|
||||
{
|
||||
return Handles.Clear();
|
||||
}
|
||||
}
|
||||
}
|
351
Ryujinx.HLE/OsHle/Handles/KProcessScheduler.cs
Normal file
351
Ryujinx.HLE/OsHle/Handles/KProcessScheduler.cs
Normal file
|
@ -0,0 +1,351 @@
|
|||
using Ryujinx.HLE.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class KProcessScheduler : IDisposable
|
||||
{
|
||||
private ConcurrentDictionary<KThread, SchedulerThread> AllThreads;
|
||||
|
||||
private ThreadQueue WaitingToRun;
|
||||
|
||||
private KThread[] CoreThreads;
|
||||
|
||||
private bool[] CoreReschedule;
|
||||
|
||||
private object SchedLock;
|
||||
|
||||
private Logger Log;
|
||||
|
||||
public KProcessScheduler(Logger Log)
|
||||
{
|
||||
this.Log = Log;
|
||||
|
||||
AllThreads = new ConcurrentDictionary<KThread, SchedulerThread>();
|
||||
|
||||
WaitingToRun = new ThreadQueue();
|
||||
|
||||
CoreThreads = new KThread[4];
|
||||
|
||||
CoreReschedule = new bool[4];
|
||||
|
||||
SchedLock = new object();
|
||||
}
|
||||
|
||||
public void StartThread(KThread Thread)
|
||||
{
|
||||
lock (SchedLock)
|
||||
{
|
||||
SchedulerThread SchedThread = new SchedulerThread(Thread);
|
||||
|
||||
if (!AllThreads.TryAdd(Thread, SchedThread))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryAddToCore(Thread))
|
||||
{
|
||||
Thread.Thread.Execute();
|
||||
|
||||
PrintDbgThreadInfo(Thread, "running.");
|
||||
}
|
||||
else
|
||||
{
|
||||
WaitingToRun.Push(SchedThread);
|
||||
|
||||
PrintDbgThreadInfo(Thread, "waiting to run.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveThread(KThread Thread)
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "exited.");
|
||||
|
||||
lock (SchedLock)
|
||||
{
|
||||
if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread))
|
||||
{
|
||||
WaitingToRun.Remove(SchedThread);
|
||||
|
||||
SchedThread.Dispose();
|
||||
}
|
||||
|
||||
int ActualCore = Thread.ActualCore;
|
||||
|
||||
SchedulerThread NewThread = WaitingToRun.Pop(ActualCore);
|
||||
|
||||
if (NewThread == null)
|
||||
{
|
||||
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ActualCore}!");
|
||||
|
||||
CoreThreads[ActualCore] = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NewThread.Thread.ActualCore = ActualCore;
|
||||
|
||||
RunThread(NewThread);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetThreadActivity(KThread Thread, bool Active)
|
||||
{
|
||||
if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
SchedThread.IsActive = Active;
|
||||
|
||||
if (Active)
|
||||
{
|
||||
SchedThread.WaitActivity.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
SchedThread.WaitActivity.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void EnterWait(KThread Thread, int TimeoutMs = Timeout.Infinite)
|
||||
{
|
||||
SchedulerThread SchedThread = AllThreads[Thread];
|
||||
|
||||
Suspend(Thread);
|
||||
|
||||
SchedThread.WaitSync.WaitOne(TimeoutMs);
|
||||
|
||||
TryResumingExecution(SchedThread);
|
||||
}
|
||||
|
||||
public void WakeUp(KThread Thread)
|
||||
{
|
||||
AllThreads[Thread].WaitSync.Set();
|
||||
}
|
||||
|
||||
public void TryToRun(KThread Thread)
|
||||
{
|
||||
lock (SchedLock)
|
||||
{
|
||||
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
|
||||
{
|
||||
if (WaitingToRun.HasThread(SchedThread) && TryAddToCore(Thread))
|
||||
{
|
||||
RunThread(SchedThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetReschedule(Thread.ProcessorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Suspend(KThread Thread)
|
||||
{
|
||||
lock (SchedLock)
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "suspended.");
|
||||
|
||||
int ActualCore = Thread.ActualCore;
|
||||
|
||||
CoreReschedule[ActualCore] = false;
|
||||
|
||||
SchedulerThread SchedThread = WaitingToRun.Pop(ActualCore);
|
||||
|
||||
if (SchedThread != null)
|
||||
{
|
||||
SchedThread.Thread.ActualCore = ActualCore;
|
||||
|
||||
CoreThreads[ActualCore] = SchedThread.Thread;
|
||||
|
||||
RunThread(SchedThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!");
|
||||
|
||||
CoreThreads[ActualCore] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetReschedule(int Core)
|
||||
{
|
||||
lock (SchedLock)
|
||||
{
|
||||
CoreReschedule[Core] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reschedule(KThread Thread)
|
||||
{
|
||||
bool NeedsReschedule;
|
||||
|
||||
lock (SchedLock)
|
||||
{
|
||||
int ActualCore = Thread.ActualCore;
|
||||
|
||||
NeedsReschedule = CoreReschedule[ActualCore];
|
||||
|
||||
CoreReschedule[ActualCore] = false;
|
||||
}
|
||||
|
||||
if (NeedsReschedule)
|
||||
{
|
||||
Yield(Thread, Thread.ActualPriority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Yield(KThread Thread)
|
||||
{
|
||||
Yield(Thread, Thread.ActualPriority);
|
||||
}
|
||||
|
||||
private void Yield(KThread Thread, int MinPriority)
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "yielded execution.");
|
||||
|
||||
lock (SchedLock)
|
||||
{
|
||||
int ActualCore = Thread.ActualCore;
|
||||
|
||||
SchedulerThread NewThread = WaitingToRun.Pop(ActualCore, MinPriority);
|
||||
|
||||
if (NewThread == null)
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "resumed because theres nothing better to run.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NewThread.Thread.ActualCore = ActualCore;
|
||||
|
||||
CoreThreads[ActualCore] = NewThread.Thread;
|
||||
|
||||
RunThread(NewThread);
|
||||
}
|
||||
|
||||
Resume(Thread);
|
||||
}
|
||||
|
||||
public void Resume(KThread Thread)
|
||||
{
|
||||
TryResumingExecution(AllThreads[Thread]);
|
||||
}
|
||||
|
||||
private void TryResumingExecution(SchedulerThread SchedThread)
|
||||
{
|
||||
KThread Thread = SchedThread.Thread;
|
||||
|
||||
PrintDbgThreadInfo(Thread, "trying to resume...");
|
||||
|
||||
SchedThread.WaitActivity.WaitOne();
|
||||
|
||||
lock (SchedLock)
|
||||
{
|
||||
if (TryAddToCore(Thread))
|
||||
{
|
||||
PrintDbgThreadInfo(Thread, "resuming execution...");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WaitingToRun.Push(SchedThread);
|
||||
|
||||
SetReschedule(Thread.ProcessorId);
|
||||
|
||||
PrintDbgThreadInfo(Thread, "entering wait state...");
|
||||
}
|
||||
|
||||
SchedThread.WaitSched.WaitOne();
|
||||
|
||||
PrintDbgThreadInfo(Thread, "resuming execution...");
|
||||
}
|
||||
|
||||
private void RunThread(SchedulerThread SchedThread)
|
||||
{
|
||||
if (!SchedThread.Thread.Thread.Execute())
|
||||
{
|
||||
PrintDbgThreadInfo(SchedThread.Thread, "waked.");
|
||||
|
||||
SchedThread.WaitSched.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintDbgThreadInfo(SchedThread.Thread, "running.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Resort(KThread Thread)
|
||||
{
|
||||
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
|
||||
{
|
||||
WaitingToRun.Resort(SchedThread);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryAddToCore(KThread Thread)
|
||||
{
|
||||
//First, try running it on Ideal Core.
|
||||
int IdealCore = Thread.IdealCore;
|
||||
|
||||
if (IdealCore != -1 && CoreThreads[IdealCore] == null)
|
||||
{
|
||||
Thread.ActualCore = IdealCore;
|
||||
|
||||
CoreThreads[IdealCore] = Thread;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//If that fails, then try running on any core allowed by Core Mask.
|
||||
int CoreMask = Thread.CoreMask;
|
||||
|
||||
for (int Core = 0; Core < CoreThreads.Length; Core++, CoreMask >>= 1)
|
||||
{
|
||||
if ((CoreMask & 1) != 0 && CoreThreads[Core] == null)
|
||||
{
|
||||
Thread.ActualCore = Core;
|
||||
|
||||
CoreThreads[Core] = Thread;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void PrintDbgThreadInfo(KThread Thread, string Message)
|
||||
{
|
||||
Log.PrintDebug(LogClass.KernelScheduler, "(" +
|
||||
"ThreadId = " + Thread.ThreadId + ", " +
|
||||
"CoreMask = 0x" + Thread.CoreMask.ToString("x1") + ", " +
|
||||
"ActualCore = " + Thread.ActualCore + ", " +
|
||||
"IdealCore = " + Thread.IdealCore + ", " +
|
||||
"ActualPriority = " + Thread.ActualPriority + ", " +
|
||||
"WantedPriority = " + Thread.WantedPriority + ") " + Message);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
foreach (SchedulerThread SchedThread in AllThreads.Values)
|
||||
{
|
||||
SchedThread.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
Ryujinx.HLE/OsHle/Handles/KSession.cs
Normal file
31
Ryujinx.HLE/OsHle/Handles/KSession.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using Ryujinx.HLE.OsHle.Services;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class KSession : IDisposable
|
||||
{
|
||||
public IpcService Service { get; private set; }
|
||||
|
||||
public string ServiceName { get; private set; }
|
||||
|
||||
public KSession(IpcService Service, string ServiceName)
|
||||
{
|
||||
this.Service = Service;
|
||||
this.ServiceName = ServiceName;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing && Service is IDisposable DisposableService)
|
||||
{
|
||||
DisposableService.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
Ryujinx.HLE/OsHle/Handles/KSynchronizationObject.cs
Normal file
28
Ryujinx.HLE/OsHle/Handles/KSynchronizationObject.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class KSynchronizationObject : IDisposable
|
||||
{
|
||||
public ManualResetEvent WaitEvent { get; private set; }
|
||||
|
||||
public KSynchronizationObject()
|
||||
{
|
||||
WaitEvent = new ManualResetEvent(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
WaitEvent.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
86
Ryujinx.HLE/OsHle/Handles/KThread.cs
Normal file
86
Ryujinx.HLE/OsHle/Handles/KThread.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
using ChocolArm64;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class KThread : KSynchronizationObject
|
||||
{
|
||||
public AThread Thread { get; private set; }
|
||||
|
||||
public int CoreMask { get; set; }
|
||||
|
||||
public long MutexAddress { get; set; }
|
||||
public long CondVarAddress { get; set; }
|
||||
|
||||
public bool CondVarSignaled { get; set; }
|
||||
|
||||
private Process Process;
|
||||
|
||||
public List<KThread> MutexWaiters { get; private set; }
|
||||
|
||||
public KThread MutexOwner { get; set; }
|
||||
|
||||
public int ActualPriority { get; private set; }
|
||||
public int WantedPriority { get; private set; }
|
||||
|
||||
public int ActualCore { get; set; }
|
||||
public int ProcessorId { get; set; }
|
||||
public int IdealCore { get; set; }
|
||||
|
||||
public int WaitHandle { get; set; }
|
||||
|
||||
public int ThreadId => Thread.ThreadId;
|
||||
|
||||
public KThread(
|
||||
AThread Thread,
|
||||
Process Process,
|
||||
int ProcessorId,
|
||||
int Priority)
|
||||
{
|
||||
this.Thread = Thread;
|
||||
this.Process = Process;
|
||||
this.ProcessorId = ProcessorId;
|
||||
this.IdealCore = ProcessorId;
|
||||
|
||||
MutexWaiters = new List<KThread>();
|
||||
|
||||
CoreMask = 1 << ProcessorId;
|
||||
|
||||
ActualPriority = WantedPriority = Priority;
|
||||
}
|
||||
|
||||
public void SetPriority(int Priority)
|
||||
{
|
||||
WantedPriority = Priority;
|
||||
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
public void UpdatePriority()
|
||||
{
|
||||
int OldPriority = ActualPriority;
|
||||
|
||||
int CurrPriority = WantedPriority;
|
||||
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
foreach (KThread Thread in MutexWaiters)
|
||||
{
|
||||
if (CurrPriority > Thread.WantedPriority)
|
||||
{
|
||||
CurrPriority = Thread.WantedPriority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrPriority != OldPriority)
|
||||
{
|
||||
ActualPriority = CurrPriority;
|
||||
|
||||
Process.Scheduler.Resort(this);
|
||||
|
||||
MutexOwner?.UpdatePriority();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
Ryujinx.HLE/OsHle/Handles/SchedulerThread.cs
Normal file
48
Ryujinx.HLE/OsHle/Handles/SchedulerThread.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class SchedulerThread : IDisposable
|
||||
{
|
||||
public KThread Thread { get; private set; }
|
||||
|
||||
public SchedulerThread Next { get; set; }
|
||||
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
public AutoResetEvent WaitSync { get; private set; }
|
||||
public ManualResetEvent WaitActivity { get; private set; }
|
||||
public AutoResetEvent WaitSched { get; private set; }
|
||||
|
||||
public SchedulerThread(KThread Thread)
|
||||
{
|
||||
this.Thread = Thread;
|
||||
|
||||
IsActive = true;
|
||||
|
||||
WaitSync = new AutoResetEvent(false);
|
||||
|
||||
WaitActivity = new ManualResetEvent(true);
|
||||
|
||||
WaitSched = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
WaitSync.Dispose();
|
||||
|
||||
WaitActivity.Dispose();
|
||||
|
||||
WaitSched.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
158
Ryujinx.HLE/OsHle/Handles/ThreadQueue.cs
Normal file
158
Ryujinx.HLE/OsHle/Handles/ThreadQueue.cs
Normal file
|
@ -0,0 +1,158 @@
|
|||
namespace Ryujinx.HLE.OsHle.Handles
|
||||
{
|
||||
class ThreadQueue
|
||||
{
|
||||
private const int LowestPriority = 0x3f;
|
||||
|
||||
private SchedulerThread Head;
|
||||
|
||||
private object ListLock;
|
||||
|
||||
public ThreadQueue()
|
||||
{
|
||||
ListLock = new object();
|
||||
}
|
||||
|
||||
public void Push(SchedulerThread Wait)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
//Ensure that we're not creating circular references
|
||||
//by adding a thread that is already on the list.
|
||||
if (HasThread(Wait))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Head == null || Head.Thread.ActualPriority > Wait.Thread.ActualPriority)
|
||||
{
|
||||
Wait.Next = Head;
|
||||
|
||||
Head = Wait;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SchedulerThread Curr = Head;
|
||||
|
||||
while (Curr.Next != null)
|
||||
{
|
||||
if (Curr.Next.Thread.ActualPriority > Wait.Thread.ActualPriority)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
|
||||
Wait.Next = Curr.Next;
|
||||
Curr.Next = Wait;
|
||||
}
|
||||
}
|
||||
|
||||
public SchedulerThread Pop(int Core, int MinPriority = LowestPriority)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
int CoreMask = 1 << Core;
|
||||
|
||||
SchedulerThread Prev = null;
|
||||
SchedulerThread Curr = Head;
|
||||
|
||||
while (Curr != null)
|
||||
{
|
||||
KThread Thread = Curr.Thread;
|
||||
|
||||
if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0)
|
||||
{
|
||||
if (Prev != null)
|
||||
{
|
||||
Prev.Next = Curr.Next;
|
||||
}
|
||||
else
|
||||
{
|
||||
Head = Head.Next;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Prev = Curr;
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
|
||||
return Curr;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(SchedulerThread Thread)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
if (Head == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (Head == Thread)
|
||||
{
|
||||
Head = Head.Next;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SchedulerThread Prev = Head;
|
||||
SchedulerThread Curr = Head.Next;
|
||||
|
||||
while (Curr != null)
|
||||
{
|
||||
if (Curr == Thread)
|
||||
{
|
||||
Prev.Next = Curr.Next;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Prev = Curr;
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Resort(SchedulerThread Thread)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
if (Remove(Thread))
|
||||
{
|
||||
Push(Thread);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasThread(SchedulerThread Thread)
|
||||
{
|
||||
lock (ListLock)
|
||||
{
|
||||
SchedulerThread Curr = Head;
|
||||
|
||||
while (Curr != null)
|
||||
{
|
||||
if (Curr == Thread)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
Ryujinx.HLE/OsHle/Homebrew.cs
Normal file
69
Ryujinx.HLE/OsHle/Homebrew.cs
Normal file
|
@ -0,0 +1,69 @@
|
|||
using ChocolArm64.Memory;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
static class Homebrew
|
||||
{
|
||||
//http://switchbrew.org/index.php?title=Homebrew_ABI
|
||||
public static void WriteHbAbiData(AMemory Memory, long Position, int MainThreadHandle)
|
||||
{
|
||||
Memory.Manager.Map(Position, AMemoryMgr.PageSize, (int)MemoryType.Normal, AMemoryPerm.RW);
|
||||
|
||||
//MainThreadHandle
|
||||
WriteConfigEntry(Memory, ref Position, 1, 0, MainThreadHandle);
|
||||
|
||||
//NextLoadPath
|
||||
WriteConfigEntry(Memory, ref Position, 2, 0, Position + 0x200, Position + 0x400);
|
||||
|
||||
//AppletType
|
||||
WriteConfigEntry(Memory, ref Position, 7);
|
||||
|
||||
//EndOfList
|
||||
WriteConfigEntry(Memory, ref Position, 0);
|
||||
}
|
||||
|
||||
private static void WriteConfigEntry(
|
||||
AMemory Memory,
|
||||
ref long Position,
|
||||
int Key,
|
||||
int Flags = 0,
|
||||
long Value0 = 0,
|
||||
long Value1 = 0)
|
||||
{
|
||||
Memory.WriteInt32(Position + 0x00, Key);
|
||||
Memory.WriteInt32(Position + 0x04, Flags);
|
||||
Memory.WriteInt64(Position + 0x08, Value0);
|
||||
Memory.WriteInt64(Position + 0x10, Value1);
|
||||
|
||||
Position += 0x18;
|
||||
}
|
||||
|
||||
public static string ReadHbAbiNextLoadPath(AMemory Memory, long Position)
|
||||
{
|
||||
string FileName = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
long Key = Memory.ReadInt64(Position);
|
||||
|
||||
if (Key == 2)
|
||||
{
|
||||
long Value0 = Memory.ReadInt64(Position + 0x08);
|
||||
long Value1 = Memory.ReadInt64(Position + 0x10);
|
||||
|
||||
FileName = AMemoryHelper.ReadAsciiString(Memory, Value0, Value1 - Value0);
|
||||
|
||||
break;
|
||||
}
|
||||
else if (Key == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Position += 0x18;
|
||||
}
|
||||
|
||||
return FileName;
|
||||
}
|
||||
}
|
||||
}
|
200
Ryujinx.HLE/OsHle/Horizon.cs
Normal file
200
Ryujinx.HLE/OsHle/Horizon.cs
Normal file
|
@ -0,0 +1,200 @@
|
|||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
public class Horizon : IDisposable
|
||||
{
|
||||
internal const int HidSize = 0x40000;
|
||||
internal const int FontSize = 0x50;
|
||||
|
||||
private Switch Ns;
|
||||
|
||||
private KProcessScheduler Scheduler;
|
||||
|
||||
private ConcurrentDictionary<int, Process> Processes;
|
||||
|
||||
public SystemStateMgr SystemState { get; private set; }
|
||||
|
||||
internal MemoryAllocator Allocator { get; private set; }
|
||||
|
||||
internal HSharedMem HidSharedMem { get; private set; }
|
||||
internal HSharedMem FontSharedMem { get; private set; }
|
||||
|
||||
internal KEvent VsyncEvent { get; private set; }
|
||||
|
||||
public Horizon(Switch Ns)
|
||||
{
|
||||
this.Ns = Ns;
|
||||
|
||||
Scheduler = new KProcessScheduler(Ns.Log);
|
||||
|
||||
Processes = new ConcurrentDictionary<int, Process>();
|
||||
|
||||
SystemState = new SystemStateMgr();
|
||||
|
||||
Allocator = new MemoryAllocator();
|
||||
|
||||
HidSharedMem = new HSharedMem();
|
||||
FontSharedMem = new HSharedMem();
|
||||
|
||||
VsyncEvent = new KEvent();
|
||||
}
|
||||
|
||||
public void LoadCart(string ExeFsDir, string RomFsFile = null)
|
||||
{
|
||||
if (RomFsFile != null)
|
||||
{
|
||||
Ns.VFs.LoadRomFs(RomFsFile);
|
||||
}
|
||||
|
||||
Process MainProcess = MakeProcess();
|
||||
|
||||
void LoadNso(string FileName)
|
||||
{
|
||||
foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
|
||||
{
|
||||
if (Path.GetExtension(File) != string.Empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Ns.Log.PrintInfo(LogClass.Loader, $"Loading {Path.GetFileNameWithoutExtension(File)}...");
|
||||
|
||||
using (FileStream Input = new FileStream(File, FileMode.Open))
|
||||
{
|
||||
string Name = Path.GetFileNameWithoutExtension(File);
|
||||
|
||||
Nso Program = new Nso(Input, Name);
|
||||
|
||||
MainProcess.LoadProgram(Program);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadNso("rtld");
|
||||
|
||||
MainProcess.SetEmptyArgs();
|
||||
|
||||
LoadNso("main");
|
||||
LoadNso("subsdk*");
|
||||
LoadNso("sdk");
|
||||
|
||||
MainProcess.Run();
|
||||
}
|
||||
|
||||
public void LoadProgram(string FileName)
|
||||
{
|
||||
bool IsNro = Path.GetExtension(FileName).ToLower() == ".nro";
|
||||
|
||||
string Name = Path.GetFileNameWithoutExtension(FileName);
|
||||
|
||||
Process MainProcess = MakeProcess();
|
||||
|
||||
using (FileStream Input = new FileStream(FileName, FileMode.Open))
|
||||
{
|
||||
MainProcess.LoadProgram(IsNro
|
||||
? (IExecutable)new Nro(Input, Name)
|
||||
: (IExecutable)new Nso(Input, Name));
|
||||
}
|
||||
|
||||
MainProcess.SetEmptyArgs();
|
||||
MainProcess.Run(IsNro);
|
||||
}
|
||||
|
||||
public void SignalVsync() => VsyncEvent.WaitEvent.Set();
|
||||
|
||||
private Process MakeProcess()
|
||||
{
|
||||
Process Process;
|
||||
|
||||
lock (Processes)
|
||||
{
|
||||
int ProcessId = 0;
|
||||
|
||||
while (Processes.ContainsKey(ProcessId))
|
||||
{
|
||||
ProcessId++;
|
||||
}
|
||||
|
||||
Process = new Process(Ns, Scheduler, ProcessId);
|
||||
|
||||
Processes.TryAdd(ProcessId, Process);
|
||||
}
|
||||
|
||||
InitializeProcess(Process);
|
||||
|
||||
return Process;
|
||||
}
|
||||
|
||||
private void InitializeProcess(Process Process)
|
||||
{
|
||||
Process.AppletState.SetFocus(true);
|
||||
}
|
||||
|
||||
internal void ExitProcess(int ProcessId)
|
||||
{
|
||||
if (Processes.TryGetValue(ProcessId, out Process Process) && Process.NeedsHbAbi)
|
||||
{
|
||||
string NextNro = Homebrew.ReadHbAbiNextLoadPath(Process.Memory, Process.HbAbiDataPosition);
|
||||
|
||||
Ns.Log.PrintInfo(LogClass.Loader, $"HbAbi NextLoadPath {NextNro}");
|
||||
|
||||
if (NextNro == string.Empty)
|
||||
{
|
||||
NextNro = "sdmc:/hbmenu.nro";
|
||||
}
|
||||
|
||||
NextNro = NextNro.Replace("sdmc:", string.Empty);
|
||||
|
||||
NextNro = Ns.VFs.GetFullPath(Ns.VFs.GetSdCardPath(), NextNro);
|
||||
|
||||
if (File.Exists(NextNro))
|
||||
{
|
||||
LoadProgram(NextNro);
|
||||
}
|
||||
}
|
||||
|
||||
if (Processes.TryRemove(ProcessId, out Process))
|
||||
{
|
||||
Process.StopAllThreadsAsync();
|
||||
Process.Dispose();
|
||||
|
||||
if (Processes.Count == 0)
|
||||
{
|
||||
Ns.OnFinish(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool TryGetProcess(int ProcessId, out Process Process)
|
||||
{
|
||||
return Processes.TryGetValue(ProcessId, out Process);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
foreach (Process Process in Processes.Values)
|
||||
{
|
||||
Process.StopAllThreadsAsync();
|
||||
Process.Dispose();
|
||||
}
|
||||
|
||||
VsyncEvent.Dispose();
|
||||
|
||||
Scheduler.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
Ryujinx.HLE/OsHle/IdDictionary.cs
Normal file
87
Ryujinx.HLE/OsHle/IdDictionary.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
class IdDictionary
|
||||
{
|
||||
private ConcurrentDictionary<int, object> Objs;
|
||||
|
||||
private int FreeIdHint = 1;
|
||||
|
||||
public IdDictionary()
|
||||
{
|
||||
Objs = new ConcurrentDictionary<int, object>();
|
||||
}
|
||||
|
||||
public bool Add(int Id, object Data)
|
||||
{
|
||||
return Objs.TryAdd(Id, Data);
|
||||
}
|
||||
|
||||
public int Add(object Data)
|
||||
{
|
||||
if (Objs.TryAdd(FreeIdHint, Data))
|
||||
{
|
||||
return FreeIdHint++;
|
||||
}
|
||||
|
||||
return AddSlow(Data);
|
||||
}
|
||||
|
||||
private int AddSlow(object Data)
|
||||
{
|
||||
for (int Id = 1; Id < int.MaxValue; Id++)
|
||||
{
|
||||
if (Objs.TryAdd(Id, Data))
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public object GetData(int Id)
|
||||
{
|
||||
if (Objs.TryGetValue(Id, out object Data))
|
||||
{
|
||||
return Data;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public T GetData<T>(int Id)
|
||||
{
|
||||
if (Objs.TryGetValue(Id, out object Data) && Data is T)
|
||||
{
|
||||
return (T)Data;
|
||||
}
|
||||
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public object Delete(int Id)
|
||||
{
|
||||
if (Objs.TryRemove(Id, out object Obj))
|
||||
{
|
||||
FreeIdHint = Id;
|
||||
|
||||
return Obj;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ICollection<object> Clear()
|
||||
{
|
||||
ICollection<object> Values = Objs.Values;
|
||||
|
||||
Objs.Clear();
|
||||
|
||||
return Values;
|
||||
}
|
||||
}
|
||||
}
|
27
Ryujinx.HLE/OsHle/Ipc/IpcBuffDesc.cs
Normal file
27
Ryujinx.HLE/OsHle/Ipc/IpcBuffDesc.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
struct IpcBuffDesc
|
||||
{
|
||||
public long Position { get; private set; }
|
||||
public long Size { get; private set; }
|
||||
public int Flags { get; private set; }
|
||||
|
||||
public IpcBuffDesc(BinaryReader Reader)
|
||||
{
|
||||
long Word0 = Reader.ReadUInt32();
|
||||
long Word1 = Reader.ReadUInt32();
|
||||
long Word2 = Reader.ReadUInt32();
|
||||
|
||||
Position = Word1;
|
||||
Position |= (Word2 << 4) & 0x0f00000000;
|
||||
Position |= (Word2 << 34) & 0x7000000000;
|
||||
|
||||
Size = Word0;
|
||||
Size |= (Word2 << 8) & 0xf00000000;
|
||||
|
||||
Flags = (int)Word2 & 3;
|
||||
}
|
||||
}
|
||||
}
|
90
Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs
Normal file
90
Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
class IpcHandleDesc
|
||||
{
|
||||
public bool HasPId { get; private set; }
|
||||
|
||||
public long PId { get; private set; }
|
||||
|
||||
public int[] ToCopy { get; private set; }
|
||||
public int[] ToMove { get; private set; }
|
||||
|
||||
public IpcHandleDesc(BinaryReader Reader)
|
||||
{
|
||||
int Word = Reader.ReadInt32();
|
||||
|
||||
HasPId = (Word & 1) != 0;
|
||||
|
||||
ToCopy = new int[(Word >> 1) & 0xf];
|
||||
ToMove = new int[(Word >> 5) & 0xf];
|
||||
|
||||
PId = HasPId ? Reader.ReadInt64() : 0;
|
||||
|
||||
for (int Index = 0; Index < ToCopy.Length; Index++)
|
||||
{
|
||||
ToCopy[Index] = Reader.ReadInt32();
|
||||
}
|
||||
|
||||
for (int Index = 0; Index < ToMove.Length; Index++)
|
||||
{
|
||||
ToMove[Index] = Reader.ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
public IpcHandleDesc(int[] Copy, int[] Move)
|
||||
{
|
||||
ToCopy = Copy ?? throw new ArgumentNullException(nameof(Copy));
|
||||
ToMove = Move ?? throw new ArgumentNullException(nameof(Move));
|
||||
}
|
||||
|
||||
public IpcHandleDesc(int[] Copy, int[] Move, long PId) : this(Copy, Move)
|
||||
{
|
||||
this.PId = PId;
|
||||
|
||||
HasPId = true;
|
||||
}
|
||||
|
||||
public static IpcHandleDesc MakeCopy(int Handle) => new IpcHandleDesc(
|
||||
new int[] { Handle },
|
||||
new int[0]);
|
||||
|
||||
public static IpcHandleDesc MakeMove(int Handle) => new IpcHandleDesc(
|
||||
new int[0],
|
||||
new int[] { Handle });
|
||||
|
||||
public byte[] GetBytes()
|
||||
{
|
||||
using (MemoryStream MS = new MemoryStream())
|
||||
{
|
||||
BinaryWriter Writer = new BinaryWriter(MS);
|
||||
|
||||
int Word = HasPId ? 1 : 0;
|
||||
|
||||
Word |= (ToCopy.Length & 0xf) << 1;
|
||||
Word |= (ToMove.Length & 0xf) << 5;
|
||||
|
||||
Writer.Write(Word);
|
||||
|
||||
if (HasPId)
|
||||
{
|
||||
Writer.Write((long)PId);
|
||||
}
|
||||
|
||||
foreach (int Handle in ToCopy)
|
||||
{
|
||||
Writer.Write(Handle);
|
||||
}
|
||||
|
||||
foreach (int Handle in ToMove)
|
||||
{
|
||||
Writer.Write(Handle);
|
||||
}
|
||||
|
||||
return MS.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
138
Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs
Normal file
138
Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs
Normal file
|
@ -0,0 +1,138 @@
|
|||
using ChocolArm64.Memory;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
static class IpcHandler
|
||||
{
|
||||
public static long IpcCall(
|
||||
Switch Ns,
|
||||
Process Process,
|
||||
AMemory Memory,
|
||||
KSession Session,
|
||||
IpcMessage Request,
|
||||
long CmdPtr)
|
||||
{
|
||||
IpcMessage Response = new IpcMessage();
|
||||
|
||||
using (MemoryStream Raw = new MemoryStream(Request.RawData))
|
||||
{
|
||||
BinaryReader ReqReader = new BinaryReader(Raw);
|
||||
|
||||
if (Request.Type == IpcMessageType.Request)
|
||||
{
|
||||
Response.Type = IpcMessageType.Response;
|
||||
|
||||
using (MemoryStream ResMS = new MemoryStream())
|
||||
{
|
||||
BinaryWriter ResWriter = new BinaryWriter(ResMS);
|
||||
|
||||
ServiceCtx Context = new ServiceCtx(
|
||||
Ns,
|
||||
Process,
|
||||
Memory,
|
||||
Session,
|
||||
Request,
|
||||
Response,
|
||||
ReqReader,
|
||||
ResWriter);
|
||||
|
||||
Session.Service.CallMethod(Context);
|
||||
|
||||
Response.RawData = ResMS.ToArray();
|
||||
}
|
||||
}
|
||||
else if (Request.Type == IpcMessageType.Control)
|
||||
{
|
||||
long Magic = ReqReader.ReadInt64();
|
||||
long CmdId = ReqReader.ReadInt64();
|
||||
|
||||
switch (CmdId)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
Request = FillResponse(Response, 0, Session.Service.ConvertToDomain());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
Request = FillResponse(Response, 0, 0x500);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//TODO: Whats the difference between IpcDuplicateSession/Ex?
|
||||
case 2:
|
||||
case 4:
|
||||
{
|
||||
int Unknown = ReqReader.ReadInt32();
|
||||
|
||||
int Handle = Process.HandleTable.OpenHandle(Session);
|
||||
|
||||
Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
|
||||
|
||||
Request = FillResponse(Response, 0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: throw new NotImplementedException(CmdId.ToString());
|
||||
}
|
||||
}
|
||||
else if (Request.Type == IpcMessageType.CloseSession)
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException(Request.Type.ToString());
|
||||
}
|
||||
|
||||
Memory.WriteBytes(CmdPtr, Response.GetBytes(CmdPtr));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values)
|
||||
{
|
||||
using (MemoryStream MS = new MemoryStream())
|
||||
{
|
||||
BinaryWriter Writer = new BinaryWriter(MS);
|
||||
|
||||
foreach (int Value in Values)
|
||||
{
|
||||
Writer.Write(Value);
|
||||
}
|
||||
|
||||
return FillResponse(Response, Result, MS.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static IpcMessage FillResponse(IpcMessage Response, long Result, byte[] Data = null)
|
||||
{
|
||||
Response.Type = IpcMessageType.Response;
|
||||
|
||||
using (MemoryStream MS = new MemoryStream())
|
||||
{
|
||||
BinaryWriter Writer = new BinaryWriter(MS);
|
||||
|
||||
Writer.Write(IpcMagic.Sfco);
|
||||
Writer.Write(Result);
|
||||
|
||||
if (Data != null)
|
||||
{
|
||||
Writer.Write(Data);
|
||||
}
|
||||
|
||||
Response.RawData = MS.ToArray();
|
||||
}
|
||||
|
||||
return Response;
|
||||
}
|
||||
}
|
||||
}
|
8
Ryujinx.HLE/OsHle/Ipc/IpcMagic.cs
Normal file
8
Ryujinx.HLE/OsHle/Ipc/IpcMagic.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
abstract class IpcMagic
|
||||
{
|
||||
public const long Sfci = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24;
|
||||
public const long Sfco = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24;
|
||||
}
|
||||
}
|
215
Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs
Normal file
215
Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs
Normal file
|
@ -0,0 +1,215 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
class IpcMessage
|
||||
{
|
||||
public IpcMessageType Type { get; set; }
|
||||
|
||||
public IpcHandleDesc HandleDesc { get; set; }
|
||||
|
||||
public List<IpcPtrBuffDesc> PtrBuff { get; private set; }
|
||||
public List<IpcBuffDesc> SendBuff { get; private set; }
|
||||
public List<IpcBuffDesc> ReceiveBuff { get; private set; }
|
||||
public List<IpcBuffDesc> ExchangeBuff { get; private set; }
|
||||
public List<IpcRecvListBuffDesc> RecvListBuff { get; private set; }
|
||||
|
||||
public List<int> ResponseObjIds { get; private set; }
|
||||
|
||||
public byte[] RawData { get; set; }
|
||||
|
||||
public IpcMessage()
|
||||
{
|
||||
PtrBuff = new List<IpcPtrBuffDesc>();
|
||||
SendBuff = new List<IpcBuffDesc>();
|
||||
ReceiveBuff = new List<IpcBuffDesc>();
|
||||
ExchangeBuff = new List<IpcBuffDesc>();
|
||||
RecvListBuff = new List<IpcRecvListBuffDesc>();
|
||||
|
||||
ResponseObjIds = new List<int>();
|
||||
}
|
||||
|
||||
public IpcMessage(byte[] Data, long CmdPtr) : this()
|
||||
{
|
||||
using (MemoryStream MS = new MemoryStream(Data))
|
||||
{
|
||||
BinaryReader Reader = new BinaryReader(MS);
|
||||
|
||||
Initialize(Reader, CmdPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize(BinaryReader Reader, long CmdPtr)
|
||||
{
|
||||
int Word0 = Reader.ReadInt32();
|
||||
int Word1 = Reader.ReadInt32();
|
||||
|
||||
Type = (IpcMessageType)(Word0 & 0xffff);
|
||||
|
||||
int PtrBuffCount = (Word0 >> 16) & 0xf;
|
||||
int SendBuffCount = (Word0 >> 20) & 0xf;
|
||||
int RecvBuffCount = (Word0 >> 24) & 0xf;
|
||||
int XchgBuffCount = (Word0 >> 28) & 0xf;
|
||||
|
||||
int RawDataSize = (Word1 >> 0) & 0x3ff;
|
||||
int RecvListFlags = (Word1 >> 10) & 0xf;
|
||||
bool HndDescEnable = ((Word1 >> 31) & 0x1) != 0;
|
||||
|
||||
if (HndDescEnable)
|
||||
{
|
||||
HandleDesc = new IpcHandleDesc(Reader);
|
||||
}
|
||||
|
||||
for (int Index = 0; Index < PtrBuffCount; Index++)
|
||||
{
|
||||
PtrBuff.Add(new IpcPtrBuffDesc(Reader));
|
||||
}
|
||||
|
||||
void ReadBuff(List<IpcBuffDesc> Buff, int Count)
|
||||
{
|
||||
for (int Index = 0; Index < Count; Index++)
|
||||
{
|
||||
Buff.Add(new IpcBuffDesc(Reader));
|
||||
}
|
||||
}
|
||||
|
||||
ReadBuff(SendBuff, SendBuffCount);
|
||||
ReadBuff(ReceiveBuff, RecvBuffCount);
|
||||
ReadBuff(ExchangeBuff, XchgBuffCount);
|
||||
|
||||
RawDataSize *= 4;
|
||||
|
||||
long RecvListPos = Reader.BaseStream.Position + RawDataSize;
|
||||
|
||||
long Pad0 = GetPadSize16(Reader.BaseStream.Position + CmdPtr);
|
||||
|
||||
Reader.BaseStream.Seek(Pad0, SeekOrigin.Current);
|
||||
|
||||
int RecvListCount = RecvListFlags - 2;
|
||||
|
||||
if (RecvListCount == 0)
|
||||
{
|
||||
RecvListCount = 1;
|
||||
}
|
||||
else if (RecvListCount < 0)
|
||||
{
|
||||
RecvListCount = 0;
|
||||
}
|
||||
|
||||
RawData = Reader.ReadBytes(RawDataSize);
|
||||
|
||||
Reader.BaseStream.Seek(RecvListPos, SeekOrigin.Begin);
|
||||
|
||||
for (int Index = 0; Index < RecvListCount; Index++)
|
||||
{
|
||||
RecvListBuff.Add(new IpcRecvListBuffDesc(Reader));
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetBytes(long CmdPtr)
|
||||
{
|
||||
using (MemoryStream MS = new MemoryStream())
|
||||
{
|
||||
BinaryWriter Writer = new BinaryWriter(MS);
|
||||
|
||||
int Word0;
|
||||
int Word1;
|
||||
|
||||
Word0 = (int)Type;
|
||||
Word0 |= (PtrBuff.Count & 0xf) << 16;
|
||||
Word0 |= (SendBuff.Count & 0xf) << 20;
|
||||
Word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
||||
Word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
||||
|
||||
byte[] HandleData = new byte[0];
|
||||
|
||||
if (HandleDesc != null)
|
||||
{
|
||||
HandleData = HandleDesc.GetBytes();
|
||||
}
|
||||
|
||||
int DataLength = RawData?.Length ?? 0;
|
||||
|
||||
int Pad0 = (int)GetPadSize16(CmdPtr + 8 + HandleData.Length);
|
||||
|
||||
//Apparently, padding after Raw Data is 16 bytes, however when there is
|
||||
//padding before Raw Data too, we need to subtract the size of this padding.
|
||||
//This is the weirdest padding I've seen so far...
|
||||
int Pad1 = 0x10 - Pad0;
|
||||
|
||||
DataLength = (DataLength + Pad0 + Pad1) / 4;
|
||||
|
||||
Word1 = DataLength & 0x3ff;
|
||||
|
||||
if (HandleDesc != null)
|
||||
{
|
||||
Word1 |= 1 << 31;
|
||||
}
|
||||
|
||||
Writer.Write(Word0);
|
||||
Writer.Write(Word1);
|
||||
Writer.Write(HandleData);
|
||||
|
||||
MS.Seek(Pad0, SeekOrigin.Current);
|
||||
|
||||
if (RawData != null)
|
||||
{
|
||||
Writer.Write(RawData);
|
||||
}
|
||||
|
||||
Writer.Write(new byte[Pad1]);
|
||||
|
||||
return MS.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private long GetPadSize16(long Position)
|
||||
{
|
||||
if ((Position & 0xf) != 0)
|
||||
{
|
||||
return 0x10 - (Position & 0xf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public (long Position, long Size) GetBufferType0x21()
|
||||
{
|
||||
if (PtrBuff.Count != 0 &&
|
||||
PtrBuff[0].Position != 0 &&
|
||||
PtrBuff[0].Size != 0)
|
||||
{
|
||||
return (PtrBuff[0].Position, PtrBuff[0].Size);
|
||||
}
|
||||
|
||||
if (SendBuff.Count != 0 &&
|
||||
SendBuff[0].Position != 0 &&
|
||||
SendBuff[0].Size != 0)
|
||||
{
|
||||
return (SendBuff[0].Position, SendBuff[0].Size);
|
||||
}
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
public (long Position, long Size) GetBufferType0x22()
|
||||
{
|
||||
if (RecvListBuff.Count != 0 &&
|
||||
RecvListBuff[0].Position != 0 &&
|
||||
RecvListBuff[0].Size != 0)
|
||||
{
|
||||
return (RecvListBuff[0].Position, RecvListBuff[0].Size);
|
||||
}
|
||||
|
||||
if (ReceiveBuff.Count != 0 &&
|
||||
ReceiveBuff[0].Position != 0 &&
|
||||
ReceiveBuff[0].Size != 0)
|
||||
{
|
||||
return (ReceiveBuff[0].Position, ReceiveBuff[0].Size);
|
||||
}
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs
Normal file
10
Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
enum IpcMessageType
|
||||
{
|
||||
Response = 0,
|
||||
CloseSession = 2,
|
||||
Request = 4,
|
||||
Control = 5
|
||||
}
|
||||
}
|
26
Ryujinx.HLE/OsHle/Ipc/IpcPtrBuffDesc.cs
Normal file
26
Ryujinx.HLE/OsHle/Ipc/IpcPtrBuffDesc.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
struct IpcPtrBuffDesc
|
||||
{
|
||||
public long Position { get; private set; }
|
||||
public int Index { get; private set; }
|
||||
public long Size { get; private set; }
|
||||
|
||||
public IpcPtrBuffDesc(BinaryReader Reader)
|
||||
{
|
||||
long Word0 = Reader.ReadUInt32();
|
||||
long Word1 = Reader.ReadUInt32();
|
||||
|
||||
Position = Word1;
|
||||
Position |= (Word0 << 20) & 0x0f00000000;
|
||||
Position |= (Word0 << 30) & 0x7000000000;
|
||||
|
||||
Index = ((int)Word0 >> 0) & 0x03f;
|
||||
Index |= ((int)Word0 >> 3) & 0x1c0;
|
||||
|
||||
Size = (ushort)(Word0 >> 16);
|
||||
}
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/OsHle/Ipc/IpcRecvListBuffDesc.cs
Normal file
19
Ryujinx.HLE/OsHle/Ipc/IpcRecvListBuffDesc.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
struct IpcRecvListBuffDesc
|
||||
{
|
||||
public long Position { get; private set; }
|
||||
public long Size { get; private set; }
|
||||
|
||||
public IpcRecvListBuffDesc(BinaryReader Reader)
|
||||
{
|
||||
long Value = Reader.ReadInt64();
|
||||
|
||||
Position = Value & 0xffffffffffff;
|
||||
|
||||
Size = (ushort)(Value >> 48);
|
||||
}
|
||||
}
|
||||
}
|
4
Ryujinx.HLE/OsHle/Ipc/ServiceProcessRequest.cs
Normal file
4
Ryujinx.HLE/OsHle/Ipc/ServiceProcessRequest.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.HLE.OsHle.Ipc
|
||||
{
|
||||
delegate long ServiceProcessRequest(ServiceCtx Context);
|
||||
}
|
17
Ryujinx.HLE/OsHle/Kernel/KernelErr.cs
Normal file
17
Ryujinx.HLE/OsHle/Kernel/KernelErr.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace Ryujinx.HLE.OsHle.Kernel
|
||||
{
|
||||
static class KernelErr
|
||||
{
|
||||
public const int InvalidAlignment = 102;
|
||||
public const int InvalidAddress = 106;
|
||||
public const int InvalidMemRange = 110;
|
||||
public const int InvalidPriority = 112;
|
||||
public const int InvalidCoreId = 113;
|
||||
public const int InvalidHandle = 114;
|
||||
public const int InvalidCoreMask = 116;
|
||||
public const int Timeout = 117;
|
||||
public const int Canceled = 118;
|
||||
public const int CountOutOfRange = 119;
|
||||
public const int InvalidInfo = 120;
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs
Normal file
19
Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace Ryujinx.HLE.OsHle.Kernel
|
||||
{
|
||||
static class NsTimeConverter
|
||||
{
|
||||
public static int GetTimeMs(ulong Ns)
|
||||
{
|
||||
ulong Ms = Ns / 1_000_000;
|
||||
|
||||
if (Ms < int.MaxValue)
|
||||
{
|
||||
return (int)Ms;
|
||||
}
|
||||
else
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
147
Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs
Normal file
147
Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs
Normal file
|
@ -0,0 +1,147 @@
|
|||
using ChocolArm64.Events;
|
||||
using ChocolArm64.Memory;
|
||||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Kernel
|
||||
{
|
||||
partial class SvcHandler : IDisposable
|
||||
{
|
||||
private delegate void SvcFunc(AThreadState ThreadState);
|
||||
|
||||
private Dictionary<int, SvcFunc> SvcFuncs;
|
||||
|
||||
private Switch Ns;
|
||||
private Process Process;
|
||||
private AMemory Memory;
|
||||
|
||||
private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
|
||||
|
||||
private HashSet<(HSharedMem, long)> MappedSharedMems;
|
||||
|
||||
private ulong CurrentHeapSize;
|
||||
|
||||
private const uint SelfThreadHandle = 0xffff8000;
|
||||
private const uint SelfProcessHandle = 0xffff8001;
|
||||
|
||||
private static Random Rng;
|
||||
|
||||
public SvcHandler(Switch Ns, Process Process)
|
||||
{
|
||||
SvcFuncs = new Dictionary<int, SvcFunc>()
|
||||
{
|
||||
{ 0x01, SvcSetHeapSize },
|
||||
{ 0x03, SvcSetMemoryAttribute },
|
||||
{ 0x04, SvcMapMemory },
|
||||
{ 0x05, SvcUnmapMemory },
|
||||
{ 0x06, SvcQueryMemory },
|
||||
{ 0x07, SvcExitProcess },
|
||||
{ 0x08, SvcCreateThread },
|
||||
{ 0x09, SvcStartThread },
|
||||
{ 0x0a, SvcExitThread },
|
||||
{ 0x0b, SvcSleepThread },
|
||||
{ 0x0c, SvcGetThreadPriority },
|
||||
{ 0x0d, SvcSetThreadPriority },
|
||||
{ 0x0e, SvcGetThreadCoreMask },
|
||||
{ 0x0f, SvcSetThreadCoreMask },
|
||||
{ 0x10, SvcGetCurrentProcessorNumber },
|
||||
{ 0x12, SvcClearEvent },
|
||||
{ 0x13, SvcMapSharedMemory },
|
||||
{ 0x14, SvcUnmapSharedMemory },
|
||||
{ 0x15, SvcCreateTransferMemory },
|
||||
{ 0x16, SvcCloseHandle },
|
||||
{ 0x17, SvcResetSignal },
|
||||
{ 0x18, SvcWaitSynchronization },
|
||||
{ 0x19, SvcCancelSynchronization },
|
||||
{ 0x1a, SvcArbitrateLock },
|
||||
{ 0x1b, SvcArbitrateUnlock },
|
||||
{ 0x1c, SvcWaitProcessWideKeyAtomic },
|
||||
{ 0x1d, SvcSignalProcessWideKey },
|
||||
{ 0x1e, SvcGetSystemTick },
|
||||
{ 0x1f, SvcConnectToNamedPort },
|
||||
{ 0x21, SvcSendSyncRequest },
|
||||
{ 0x22, SvcSendSyncRequestWithUserBuffer },
|
||||
{ 0x25, SvcGetThreadId },
|
||||
{ 0x26, SvcBreak },
|
||||
{ 0x27, SvcOutputDebugString },
|
||||
{ 0x29, SvcGetInfo },
|
||||
{ 0x2c, SvcMapPhysicalMemory },
|
||||
{ 0x2d, SvcUnmapPhysicalMemory },
|
||||
{ 0x32, SvcSetThreadActivity }
|
||||
};
|
||||
|
||||
this.Ns = Ns;
|
||||
this.Process = Process;
|
||||
this.Memory = Process.Memory;
|
||||
|
||||
SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
|
||||
|
||||
MappedSharedMems = new HashSet<(HSharedMem, long)>();
|
||||
}
|
||||
|
||||
static SvcHandler()
|
||||
{
|
||||
Rng = new Random();
|
||||
}
|
||||
|
||||
public void SvcCall(object sender, AInstExceptionEventArgs e)
|
||||
{
|
||||
AThreadState ThreadState = (AThreadState)sender;
|
||||
|
||||
if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
|
||||
{
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called.");
|
||||
|
||||
Func(ThreadState);
|
||||
|
||||
Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr));
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.PrintStackTrace(ThreadState);
|
||||
|
||||
throw new NotImplementedException(e.Id.ToString("x4"));
|
||||
}
|
||||
}
|
||||
|
||||
private KThread GetThread(long Tpidr, int Handle)
|
||||
{
|
||||
if ((uint)Handle == SelfThreadHandle)
|
||||
{
|
||||
return Process.GetThread(Tpidr);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Process.HandleTable.GetData<KThread>(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing)
|
||||
{
|
||||
lock (MappedSharedMems)
|
||||
{
|
||||
foreach ((HSharedMem SharedMem, long Position) in MappedSharedMems)
|
||||
{
|
||||
SharedMem.RemoveVirtualPosition(Memory, Position);
|
||||
}
|
||||
|
||||
MappedSharedMems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
285
Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs
Normal file
285
Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs
Normal file
|
@ -0,0 +1,285 @@
|
|||
using ChocolArm64.Memory;
|
||||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
|
||||
using static Ryujinx.HLE.OsHle.ErrorCode;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Kernel
|
||||
{
|
||||
partial class SvcHandler
|
||||
{
|
||||
private void SvcSetHeapSize(AThreadState ThreadState)
|
||||
{
|
||||
uint Size = (uint)ThreadState.X1;
|
||||
|
||||
long Position = MemoryRegions.HeapRegionAddress;
|
||||
|
||||
if (Size > CurrentHeapSize)
|
||||
{
|
||||
Memory.Manager.Map(Position, Size, (int)MemoryType.Heap, AMemoryPerm.RW);
|
||||
}
|
||||
else
|
||||
{
|
||||
Memory.Manager.Unmap(Position + Size, (long)CurrentHeapSize - Size);
|
||||
}
|
||||
|
||||
CurrentHeapSize = Size;
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = (ulong)Position;
|
||||
}
|
||||
|
||||
private void SvcSetMemoryAttribute(AThreadState ThreadState)
|
||||
{
|
||||
long Position = (long)ThreadState.X0;
|
||||
long Size = (long)ThreadState.X1;
|
||||
int State0 = (int)ThreadState.X2;
|
||||
int State1 = (int)ThreadState.X3;
|
||||
|
||||
if ((State0 == 0 && State1 == 0) ||
|
||||
(State0 == 8 && State1 == 0))
|
||||
{
|
||||
Memory.Manager.ClearAttrBit(Position, Size, 3);
|
||||
}
|
||||
else if (State0 == 8 && State1 == 8)
|
||||
{
|
||||
Memory.Manager.SetAttrBit(Position, Size, 3);
|
||||
}
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcMapMemory(AThreadState ThreadState)
|
||||
{
|
||||
long Dst = (long)ThreadState.X0;
|
||||
long Src = (long)ThreadState.X1;
|
||||
long Size = (long)ThreadState.X2;
|
||||
|
||||
if (!IsValidPosition(Src))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid src address {Src:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValidMapPosition(Dst))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid dst address {Dst:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AMemoryMapInfo SrcInfo = Memory.Manager.GetMapInfo(Src);
|
||||
|
||||
Memory.Manager.Map(Dst, Size, (int)MemoryType.MappedMemory, SrcInfo.Perm);
|
||||
|
||||
Memory.Manager.Reprotect(Src, Size, AMemoryPerm.None);
|
||||
|
||||
Memory.Manager.SetAttrBit(Src, Size, 0);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcUnmapMemory(AThreadState ThreadState)
|
||||
{
|
||||
long Dst = (long)ThreadState.X0;
|
||||
long Src = (long)ThreadState.X1;
|
||||
long Size = (long)ThreadState.X2;
|
||||
|
||||
if (!IsValidPosition(Src))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid src address {Src:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValidMapPosition(Dst))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid dst address {Dst:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AMemoryMapInfo DstInfo = Memory.Manager.GetMapInfo(Dst);
|
||||
|
||||
Memory.Manager.Unmap(Dst, Size, (int)MemoryType.MappedMemory);
|
||||
|
||||
Memory.Manager.Reprotect(Src, Size, DstInfo.Perm);
|
||||
|
||||
Memory.Manager.ClearAttrBit(Src, Size, 0);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcQueryMemory(AThreadState ThreadState)
|
||||
{
|
||||
long InfoPtr = (long)ThreadState.X0;
|
||||
long Position = (long)ThreadState.X2;
|
||||
|
||||
AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position);
|
||||
|
||||
if (MapInfo == null)
|
||||
{
|
||||
long AddrSpaceEnd = MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize;
|
||||
|
||||
long ReservedSize = (long)(ulong.MaxValue - (ulong)AddrSpaceEnd) + 1;
|
||||
|
||||
MapInfo = new AMemoryMapInfo(AddrSpaceEnd, ReservedSize, (int)MemoryType.Reserved, 0, AMemoryPerm.None);
|
||||
}
|
||||
|
||||
Memory.WriteInt64(InfoPtr + 0x00, MapInfo.Position);
|
||||
Memory.WriteInt64(InfoPtr + 0x08, MapInfo.Size);
|
||||
Memory.WriteInt32(InfoPtr + 0x10, MapInfo.Type);
|
||||
Memory.WriteInt32(InfoPtr + 0x14, MapInfo.Attr);
|
||||
Memory.WriteInt32(InfoPtr + 0x18, (int)MapInfo.Perm);
|
||||
Memory.WriteInt32(InfoPtr + 0x1c, 0);
|
||||
Memory.WriteInt32(InfoPtr + 0x20, 0);
|
||||
Memory.WriteInt32(InfoPtr + 0x24, 0);
|
||||
//TODO: X1.
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = 0;
|
||||
}
|
||||
|
||||
private void SvcMapSharedMemory(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
long Src = (long)ThreadState.X1;
|
||||
long Size = (long)ThreadState.X2;
|
||||
int Perm = (int)ThreadState.X3;
|
||||
|
||||
if (!IsValidPosition(Src))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
HSharedMem SharedMem = Process.HandleTable.GetData<HSharedMem>(Handle);
|
||||
|
||||
if (SharedMem != null)
|
||||
{
|
||||
Memory.Manager.Map(Src, Size, (int)MemoryType.SharedMemory, AMemoryPerm.Write);
|
||||
|
||||
AMemoryHelper.FillWithZeros(Memory, Src, (int)Size);
|
||||
|
||||
Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm);
|
||||
|
||||
lock (MappedSharedMems)
|
||||
{
|
||||
MappedSharedMems.Add((SharedMem, Src));
|
||||
}
|
||||
|
||||
SharedMem.AddVirtualPosition(Memory, Src);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
//TODO: Error codes.
|
||||
}
|
||||
|
||||
private void SvcUnmapSharedMemory(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
long Src = (long)ThreadState.X1;
|
||||
long Size = (long)ThreadState.X2;
|
||||
|
||||
if (!IsValidPosition(Src))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
HSharedMem SharedMem = Process.HandleTable.GetData<HSharedMem>(Handle);
|
||||
|
||||
if (SharedMem != null)
|
||||
{
|
||||
Memory.Manager.Unmap(Src, Size, (int)MemoryType.SharedMemory);
|
||||
|
||||
SharedMem.RemoveVirtualPosition(Memory, Src);
|
||||
|
||||
lock (MappedSharedMems)
|
||||
{
|
||||
MappedSharedMems.Remove((SharedMem, Src));
|
||||
}
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
//TODO: Error codes.
|
||||
}
|
||||
|
||||
private void SvcCreateTransferMemory(AThreadState ThreadState)
|
||||
{
|
||||
long Src = (long)ThreadState.X1;
|
||||
long Size = (long)ThreadState.X2;
|
||||
int Perm = (int)ThreadState.X3;
|
||||
|
||||
if (!IsValidPosition(Src))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Src:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Src);
|
||||
|
||||
Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm);
|
||||
|
||||
HTransferMem TMem = new HTransferMem(Memory, MapInfo.Perm, Src, Size);
|
||||
|
||||
ulong Handle = (ulong)Process.HandleTable.OpenHandle(TMem);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = Handle;
|
||||
}
|
||||
|
||||
private void SvcMapPhysicalMemory(AThreadState ThreadState)
|
||||
{
|
||||
long Position = (long)ThreadState.X0;
|
||||
uint Size = (uint)ThreadState.X1;
|
||||
|
||||
Memory.Manager.Map(Position, Size, (int)MemoryType.Heap, AMemoryPerm.RW);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcUnmapPhysicalMemory(AThreadState ThreadState)
|
||||
{
|
||||
long Position = (long)ThreadState.X0;
|
||||
uint Size = (uint)ThreadState.X1;
|
||||
|
||||
Memory.Manager.Unmap(Position, Size);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private static bool IsValidPosition(long Position)
|
||||
{
|
||||
return Position >= MemoryRegions.AddrSpaceStart &&
|
||||
Position < MemoryRegions.AddrSpaceStart + MemoryRegions.AddrSpaceSize;
|
||||
}
|
||||
|
||||
private static bool IsValidMapPosition(long Position)
|
||||
{
|
||||
return Position >= MemoryRegions.MapRegionAddress &&
|
||||
Position < MemoryRegions.MapRegionAddress + MemoryRegions.MapRegionSize;
|
||||
}
|
||||
}
|
||||
}
|
369
Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs
Normal file
369
Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs
Normal file
|
@ -0,0 +1,369 @@
|
|||
using ChocolArm64.Memory;
|
||||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Exceptions;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using Ryujinx.HLE.OsHle.Ipc;
|
||||
using Ryujinx.HLE.OsHle.Services;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.HLE.OsHle.ErrorCode;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Kernel
|
||||
{
|
||||
partial class SvcHandler
|
||||
{
|
||||
private const int AllowedCpuIdBitmask = 0b1111;
|
||||
|
||||
private const bool EnableProcessDebugging = false;
|
||||
|
||||
private const bool IsVirtualMemoryEnabled = true; //This is always true(?)
|
||||
|
||||
private void SvcExitProcess(AThreadState ThreadState)
|
||||
{
|
||||
Ns.Os.ExitProcess(ThreadState.ProcessId);
|
||||
}
|
||||
|
||||
private void SvcClearEvent(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
|
||||
//TODO: Implement events.
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcCloseHandle(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
|
||||
object Obj = Process.HandleTable.CloseHandle(Handle);
|
||||
|
||||
if (Obj == null)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Obj is KSession Session)
|
||||
{
|
||||
Session.Dispose();
|
||||
}
|
||||
else if (Obj is HTransferMem TMem)
|
||||
{
|
||||
TMem.Memory.Manager.Reprotect(
|
||||
TMem.Position,
|
||||
TMem.Size,
|
||||
TMem.Perm);
|
||||
}
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcResetSignal(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
|
||||
KEvent Event = Process.HandleTable.GetData<KEvent>(Handle);
|
||||
|
||||
if (Event != null)
|
||||
{
|
||||
Event.WaitEvent.Reset();
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid event handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcWaitSynchronization(AThreadState ThreadState)
|
||||
{
|
||||
long HandlesPtr = (long)ThreadState.X1;
|
||||
int HandlesCount = (int)ThreadState.X2;
|
||||
ulong Timeout = ThreadState.X3;
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"HandlesPtr = " + HandlesPtr .ToString("x16") + ", " +
|
||||
"HandlesCount = " + HandlesCount.ToString("x8") + ", " +
|
||||
"Timeout = " + Timeout .ToString("x16"));
|
||||
|
||||
if ((uint)HandlesCount > 0x40)
|
||||
{
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
WaitHandle[] Handles = new WaitHandle[HandlesCount + 1];
|
||||
|
||||
for (int Index = 0; Index < HandlesCount; Index++)
|
||||
{
|
||||
int Handle = Memory.ReadInt32(HandlesPtr + Index * 4);
|
||||
|
||||
KSynchronizationObject SyncObj = Process.HandleTable.GetData<KSynchronizationObject>(Handle);
|
||||
|
||||
if (SyncObj == null)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Handles[Index] = SyncObj.WaitEvent;
|
||||
}
|
||||
|
||||
using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
|
||||
{
|
||||
if (!SyncWaits.TryAdd(CurrThread, WaitEvent))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
Handles[HandlesCount] = WaitEvent;
|
||||
|
||||
Process.Scheduler.Suspend(CurrThread);
|
||||
|
||||
int HandleIndex;
|
||||
|
||||
ulong Result = 0;
|
||||
|
||||
if (Timeout != ulong.MaxValue)
|
||||
{
|
||||
HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout));
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleIndex = WaitHandle.WaitAny(Handles);
|
||||
}
|
||||
|
||||
if (HandleIndex == WaitHandle.WaitTimeout)
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
}
|
||||
else if (HandleIndex == HandlesCount)
|
||||
{
|
||||
Result = MakeError(ErrorModule.Kernel, KernelErr.Canceled);
|
||||
}
|
||||
|
||||
SyncWaits.TryRemove(CurrThread, out _);
|
||||
|
||||
Process.Scheduler.Resume(CurrThread);
|
||||
|
||||
ThreadState.X0 = Result;
|
||||
|
||||
if (Result == 0)
|
||||
{
|
||||
ThreadState.X1 = (ulong)HandleIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcCancelSynchronization(AThreadState ThreadState)
|
||||
{
|
||||
int ThreadHandle = (int)ThreadState.X0;
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle);
|
||||
|
||||
if (Thread == null)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (SyncWaits.TryRemove(Thread, out AutoResetEvent WaitEvent))
|
||||
{
|
||||
WaitEvent.Set();
|
||||
}
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcGetSystemTick(AThreadState ThreadState)
|
||||
{
|
||||
ThreadState.X0 = ThreadState.CntpctEl0;
|
||||
}
|
||||
|
||||
private void SvcConnectToNamedPort(AThreadState ThreadState)
|
||||
{
|
||||
long StackPtr = (long)ThreadState.X0;
|
||||
long NamePtr = (long)ThreadState.X1;
|
||||
|
||||
string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8);
|
||||
|
||||
//TODO: Validate that app has perms to access the service, and that the service
|
||||
//actually exists, return error codes otherwise.
|
||||
KSession Session = new KSession(ServiceFactory.MakeService(Name), Name);
|
||||
|
||||
ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = Handle;
|
||||
}
|
||||
|
||||
private void SvcSendSyncRequest(AThreadState ThreadState)
|
||||
{
|
||||
SendSyncRequest(ThreadState, ThreadState.Tpidr, 0x100, (int)ThreadState.X0);
|
||||
}
|
||||
|
||||
private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState)
|
||||
{
|
||||
SendSyncRequest(
|
||||
ThreadState,
|
||||
(long)ThreadState.X0,
|
||||
(long)ThreadState.X1,
|
||||
(int)ThreadState.X2);
|
||||
}
|
||||
|
||||
private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle)
|
||||
{
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
byte[] CmdData = Memory.ReadBytes(CmdPtr, Size);
|
||||
|
||||
KSession Session = Process.HandleTable.GetData<KSession>(Handle);
|
||||
|
||||
if (Session != null)
|
||||
{
|
||||
Process.Scheduler.Suspend(CurrThread);
|
||||
|
||||
IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
|
||||
|
||||
long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr);
|
||||
|
||||
Thread.Yield();
|
||||
|
||||
Process.Scheduler.Resume(CurrThread);
|
||||
|
||||
ThreadState.X0 = (ulong)Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcBreak(AThreadState ThreadState)
|
||||
{
|
||||
long Reason = (long)ThreadState.X0;
|
||||
long Unknown = (long)ThreadState.X1;
|
||||
long Info = (long)ThreadState.X2;
|
||||
|
||||
Process.PrintStackTrace(ThreadState);
|
||||
|
||||
throw new GuestBrokeExecutionException();
|
||||
}
|
||||
|
||||
private void SvcOutputDebugString(AThreadState ThreadState)
|
||||
{
|
||||
long Position = (long)ThreadState.X0;
|
||||
long Size = (long)ThreadState.X1;
|
||||
|
||||
string Str = AMemoryHelper.ReadAsciiString(Memory, Position, Size);
|
||||
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, Str);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcGetInfo(AThreadState ThreadState)
|
||||
{
|
||||
long StackPtr = (long)ThreadState.X0;
|
||||
int InfoType = (int)ThreadState.X1;
|
||||
long Handle = (long)ThreadState.X2;
|
||||
int InfoId = (int)ThreadState.X3;
|
||||
|
||||
//Fail for info not available on older Kernel versions.
|
||||
if (InfoType == 18 ||
|
||||
InfoType == 19 ||
|
||||
InfoType == 20)
|
||||
{
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidInfo);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (InfoType)
|
||||
{
|
||||
case 0:
|
||||
ThreadState.X1 = AllowedCpuIdBitmask;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
ThreadState.X1 = MemoryRegions.MapRegionAddress;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
ThreadState.X1 = MemoryRegions.MapRegionSize;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
ThreadState.X1 = MemoryRegions.HeapRegionAddress;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
ThreadState.X1 = MemoryRegions.HeapRegionSize;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
ThreadState.X1 = MemoryRegions.TotalMemoryAvailable;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
ThreadState.X1 = MemoryRegions.TotalMemoryUsed + CurrentHeapSize;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
ThreadState.X1 = EnableProcessDebugging ? 1 : 0;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
ThreadState.X1 = (ulong)Rng.Next() + ((ulong)Rng.Next() << 32);
|
||||
break;
|
||||
|
||||
case 12:
|
||||
ThreadState.X1 = MemoryRegions.AddrSpaceStart;
|
||||
break;
|
||||
|
||||
case 13:
|
||||
ThreadState.X1 = MemoryRegions.AddrSpaceSize;
|
||||
break;
|
||||
|
||||
case 14:
|
||||
ThreadState.X1 = MemoryRegions.MapRegionAddress;
|
||||
break;
|
||||
|
||||
case 15:
|
||||
ThreadState.X1 = MemoryRegions.MapRegionSize;
|
||||
break;
|
||||
|
||||
case 16:
|
||||
ThreadState.X1 = IsVirtualMemoryEnabled ? 1 : 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
Process.PrintStackTrace(ThreadState);
|
||||
|
||||
throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle:x8} {InfoId}");
|
||||
}
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
}
|
||||
}
|
291
Ryujinx.HLE/OsHle/Kernel/SvcThread.cs
Normal file
291
Ryujinx.HLE/OsHle/Kernel/SvcThread.cs
Normal file
|
@ -0,0 +1,291 @@
|
|||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.HLE.OsHle.ErrorCode;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Kernel
|
||||
{
|
||||
partial class SvcHandler
|
||||
{
|
||||
private void SvcCreateThread(AThreadState ThreadState)
|
||||
{
|
||||
long EntryPoint = (long)ThreadState.X1;
|
||||
long ArgsPtr = (long)ThreadState.X2;
|
||||
long StackTop = (long)ThreadState.X3;
|
||||
int Priority = (int)ThreadState.X4;
|
||||
int ProcessorId = (int)ThreadState.X5;
|
||||
|
||||
if ((uint)Priority > 0x3f)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPriority);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProcessorId == -2)
|
||||
{
|
||||
//TODO: Get this value from the NPDM file.
|
||||
ProcessorId = 0;
|
||||
}
|
||||
else if ((uint)ProcessorId > 3)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int Handle = Process.MakeThread(
|
||||
EntryPoint,
|
||||
StackTop,
|
||||
ArgsPtr,
|
||||
Priority,
|
||||
ProcessorId);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = (ulong)Handle;
|
||||
}
|
||||
|
||||
private void SvcStartThread(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
|
||||
KThread NewThread = Process.HandleTable.GetData<KThread>(Handle);
|
||||
|
||||
if (NewThread != null)
|
||||
{
|
||||
Process.Scheduler.StartThread(NewThread);
|
||||
Process.Scheduler.SetReschedule(NewThread.ProcessorId);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcExitThread(AThreadState ThreadState)
|
||||
{
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
CurrThread.Thread.StopExecution();
|
||||
}
|
||||
|
||||
private void SvcSleepThread(AThreadState ThreadState)
|
||||
{
|
||||
ulong TimeoutNs = ThreadState.X0;
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
if (TimeoutNs == 0)
|
||||
{
|
||||
Process.Scheduler.Yield(CurrThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Scheduler.Suspend(CurrThread);
|
||||
|
||||
Thread.Sleep(NsTimeConverter.GetTimeMs(TimeoutNs));
|
||||
|
||||
Process.Scheduler.Resume(CurrThread);
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcGetThreadPriority(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X1;
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = (ulong)Thread.ActualPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcSetThreadPriority(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
int Priority = (int)ThreadState.X1;
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
Thread.SetPriority(Priority);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcGetThreadCoreMask(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X2;
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "Handle = " + Handle.ToString("x8"));
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = (ulong)Thread.IdealCore;
|
||||
ThreadState.X2 = (ulong)Thread.CoreMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcSetThreadCoreMask(AThreadState ThreadState)
|
||||
{
|
||||
//FIXME: This is wrong, but the "correct" way to handle
|
||||
//this svc causes deadlocks when more often.
|
||||
//There is probably something wrong with it still.
|
||||
ThreadState.X0 = 0;
|
||||
|
||||
return;
|
||||
|
||||
int Handle = (int)ThreadState.X0;
|
||||
int IdealCore = (int)ThreadState.X1;
|
||||
long CoreMask = (long)ThreadState.X2;
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"Handle = " + Handle .ToString("x8") + ", " +
|
||||
"IdealCore = " + IdealCore.ToString("x8") + ", " +
|
||||
"CoreMask = " + CoreMask .ToString("x16"));
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
|
||||
|
||||
if (IdealCore == -2)
|
||||
{
|
||||
//TODO: Get this value from the NPDM file.
|
||||
IdealCore = 0;
|
||||
|
||||
CoreMask = 1 << IdealCore;
|
||||
}
|
||||
else if (IdealCore != -3)
|
||||
{
|
||||
if ((uint)IdealCore > 3)
|
||||
{
|
||||
if ((IdealCore | 2) != -1)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ((CoreMask & (1 << IdealCore)) == 0)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreMask);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Thread == null)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//-1 is used as "don't care", so the IdealCore value is ignored.
|
||||
//-2 is used as "use NPDM default core id" (handled above).
|
||||
//-3 is used as "don't update", the old IdealCore value is kept.
|
||||
if (IdealCore != -3)
|
||||
{
|
||||
Thread.IdealCore = IdealCore;
|
||||
}
|
||||
else if ((CoreMask & (1 << Thread.IdealCore)) == 0)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreMask);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Thread.CoreMask = (int)CoreMask;
|
||||
|
||||
Process.Scheduler.TryToRun(Thread);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcGetCurrentProcessorNumber(AThreadState ThreadState)
|
||||
{
|
||||
ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore;
|
||||
}
|
||||
|
||||
private void SvcGetThreadId(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X1;
|
||||
|
||||
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
ThreadState.X0 = 0;
|
||||
ThreadState.X1 = (ulong)Thread.ThreadId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void SvcSetThreadActivity(AThreadState ThreadState)
|
||||
{
|
||||
int Handle = (int)ThreadState.X0;
|
||||
bool Active = (int)ThreadState.X1 == 0;
|
||||
|
||||
KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
Process.Scheduler.SetThreadActivity(Thread, Active);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
435
Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs
Normal file
435
Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs
Normal file
|
@ -0,0 +1,435 @@
|
|||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.HLE.OsHle.ErrorCode;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Kernel
|
||||
{
|
||||
partial class SvcHandler
|
||||
{
|
||||
private const int MutexHasListenersMask = 0x40000000;
|
||||
|
||||
private void SvcArbitrateLock(AThreadState ThreadState)
|
||||
{
|
||||
int OwnerThreadHandle = (int)ThreadState.X0;
|
||||
long MutexAddress = (long)ThreadState.X1;
|
||||
int WaitThreadHandle = (int)ThreadState.X2;
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"OwnerThreadHandle = " + OwnerThreadHandle.ToString("x8") + ", " +
|
||||
"MutexAddress = " + MutexAddress .ToString("x16") + ", " +
|
||||
"WaitThreadHandle = " + WaitThreadHandle .ToString("x8"));
|
||||
|
||||
if (IsPointingInsideKernel(MutexAddress))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsWordAddressUnaligned(MutexAddress))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
|
||||
|
||||
if (OwnerThread == null)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KThread WaitThread = Process.HandleTable.GetData<KThread>(WaitThreadHandle);
|
||||
|
||||
if (WaitThread == null)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcArbitrateUnlock(AThreadState ThreadState)
|
||||
{
|
||||
long MutexAddress = (long)ThreadState.X0;
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = " + MutexAddress.ToString("x16"));
|
||||
|
||||
if (IsPointingInsideKernel(MutexAddress))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsWordAddressUnaligned(MutexAddress))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
|
||||
{
|
||||
long MutexAddress = (long)ThreadState.X0;
|
||||
long CondVarAddress = (long)ThreadState.X1;
|
||||
int ThreadHandle = (int)ThreadState.X2;
|
||||
ulong Timeout = ThreadState.X3;
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"MutexAddress = " + MutexAddress .ToString("x16") + ", " +
|
||||
"CondVarAddress = " + CondVarAddress.ToString("x16") + ", " +
|
||||
"ThreadHandle = " + ThreadHandle .ToString("x8") + ", " +
|
||||
"Timeout = " + Timeout .ToString("x16"));
|
||||
|
||||
if (IsPointingInsideKernel(MutexAddress))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsWordAddressUnaligned(MutexAddress))
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
|
||||
|
||||
if (Thread == null)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
|
||||
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
MutexUnlock(CurrThread, MutexAddress);
|
||||
|
||||
if (!CondVarWait(CurrThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout))
|
||||
{
|
||||
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void SvcSignalProcessWideKey(AThreadState ThreadState)
|
||||
{
|
||||
long CondVarAddress = (long)ThreadState.X0;
|
||||
int Count = (int)ThreadState.X1;
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc,
|
||||
"CondVarAddress = " + CondVarAddress.ToString("x16") + ", " +
|
||||
"Count = " + Count .ToString("x8"));
|
||||
|
||||
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
|
||||
|
||||
CondVarSignal(CurrThread, CondVarAddress, Count);
|
||||
|
||||
ThreadState.X0 = 0;
|
||||
}
|
||||
|
||||
private void MutexLock(
|
||||
KThread CurrThread,
|
||||
KThread WaitThread,
|
||||
int OwnerThreadHandle,
|
||||
int WaitThreadHandle,
|
||||
long MutexAddress)
|
||||
{
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
int MutexValue = Process.Memory.ReadInt32(MutexAddress);
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
|
||||
|
||||
if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrThread.WaitHandle = WaitThreadHandle;
|
||||
CurrThread.MutexAddress = MutexAddress;
|
||||
|
||||
InsertWaitingMutexThread(OwnerThreadHandle, WaitThread);
|
||||
}
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
|
||||
|
||||
Process.Scheduler.EnterWait(CurrThread);
|
||||
}
|
||||
|
||||
private void MutexUnlock(KThread CurrThread, long MutexAddress)
|
||||
{
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
//This is the new thread that will now own the mutex.
|
||||
//If no threads are waiting for the lock, then it should be null.
|
||||
KThread OwnerThread = PopThread(CurrThread.MutexWaiters, x => x.MutexAddress == MutexAddress);
|
||||
|
||||
if (OwnerThread != null)
|
||||
{
|
||||
//Remove all waiting mutex from the old owner,
|
||||
//and insert then on the new owner.
|
||||
UpdateMutexOwner(CurrThread, OwnerThread, MutexAddress);
|
||||
|
||||
CurrThread.UpdatePriority();
|
||||
|
||||
int HasListeners = OwnerThread.MutexWaiters.Count > 0 ? MutexHasListenersMask : 0;
|
||||
|
||||
Process.Memory.WriteInt32(MutexAddress, HasListeners | OwnerThread.WaitHandle);
|
||||
|
||||
OwnerThread.WaitHandle = 0;
|
||||
OwnerThread.MutexAddress = 0;
|
||||
OwnerThread.CondVarAddress = 0;
|
||||
|
||||
OwnerThread.MutexOwner = null;
|
||||
|
||||
OwnerThread.UpdatePriority();
|
||||
|
||||
Process.Scheduler.WakeUp(OwnerThread);
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Memory.WriteInt32(MutexAddress, 0);
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "No threads waiting mutex!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CondVarWait(
|
||||
KThread WaitThread,
|
||||
int WaitThreadHandle,
|
||||
long MutexAddress,
|
||||
long CondVarAddress,
|
||||
ulong Timeout)
|
||||
{
|
||||
WaitThread.WaitHandle = WaitThreadHandle;
|
||||
WaitThread.MutexAddress = MutexAddress;
|
||||
WaitThread.CondVarAddress = CondVarAddress;
|
||||
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
WaitThread.CondVarSignaled = false;
|
||||
|
||||
Process.ThreadArbiterList.Add(WaitThread);
|
||||
}
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
|
||||
|
||||
if (Timeout != ulong.MaxValue)
|
||||
{
|
||||
Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
|
||||
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
WaitThread.MutexOwner?.MutexWaiters.Remove(WaitThread);
|
||||
|
||||
if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
|
||||
{
|
||||
WaitThread.MutexOwner = null;
|
||||
|
||||
Process.ThreadArbiterList.Remove(WaitThread);
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Scheduler.EnterWait(WaitThread);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CondVarSignal(KThread CurrThread, long CondVarAddress, int Count)
|
||||
{
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
while (Count == -1 || Count-- > 0)
|
||||
{
|
||||
KThread WaitThread = PopThread(Process.ThreadArbiterList, x => x.CondVarAddress == CondVarAddress);
|
||||
|
||||
if (WaitThread == null)
|
||||
{
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
WaitThread.CondVarSignaled = true;
|
||||
|
||||
AcquireMutexValue(WaitThread.MutexAddress);
|
||||
|
||||
int MutexValue = Process.Memory.ReadInt32(WaitThread.MutexAddress);
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
|
||||
|
||||
if (MutexValue == 0)
|
||||
{
|
||||
//Give the lock to this thread.
|
||||
Process.Memory.WriteInt32(WaitThread.MutexAddress, WaitThread.WaitHandle);
|
||||
|
||||
WaitThread.WaitHandle = 0;
|
||||
WaitThread.MutexAddress = 0;
|
||||
WaitThread.CondVarAddress = 0;
|
||||
|
||||
WaitThread.MutexOwner?.UpdatePriority();
|
||||
|
||||
WaitThread.MutexOwner = null;
|
||||
|
||||
Process.Scheduler.WakeUp(WaitThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Wait until the lock is released.
|
||||
MutexValue &= ~MutexHasListenersMask;
|
||||
|
||||
InsertWaitingMutexThread(MutexValue, WaitThread);
|
||||
|
||||
MutexValue |= MutexHasListenersMask;
|
||||
|
||||
Process.Memory.WriteInt32(WaitThread.MutexAddress, MutexValue);
|
||||
}
|
||||
|
||||
ReleaseMutexValue(WaitThread.MutexAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMutexOwner(KThread CurrThread, KThread NewOwner, long MutexAddress)
|
||||
{
|
||||
//Go through all threads waiting for the mutex,
|
||||
//and update the MutexOwner field to point to the new owner.
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
|
||||
{
|
||||
KThread Thread = CurrThread.MutexWaiters[Index];
|
||||
|
||||
if (Thread.MutexAddress == MutexAddress)
|
||||
{
|
||||
CurrThread.MutexWaiters.RemoveAt(Index--);
|
||||
|
||||
Thread.MutexOwner = NewOwner;
|
||||
|
||||
InsertWaitingMutexThread(NewOwner, Thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertWaitingMutexThread(int OwnerThreadHandle, KThread WaitThread)
|
||||
{
|
||||
KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
|
||||
|
||||
if (OwnerThread == null)
|
||||
{
|
||||
Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
InsertWaitingMutexThread(OwnerThread, WaitThread);
|
||||
}
|
||||
|
||||
private void InsertWaitingMutexThread(KThread OwnerThread, KThread WaitThread)
|
||||
{
|
||||
lock (Process.ThreadSyncLock)
|
||||
{
|
||||
WaitThread.MutexOwner = OwnerThread;
|
||||
|
||||
if (!OwnerThread.MutexWaiters.Contains(WaitThread))
|
||||
{
|
||||
OwnerThread.MutexWaiters.Add(WaitThread);
|
||||
|
||||
OwnerThread.UpdatePriority();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private KThread PopThread(List<KThread> Threads, Func<KThread, bool> Predicate)
|
||||
{
|
||||
KThread Thread = Threads.OrderBy(x => x.ActualPriority).FirstOrDefault(Predicate);
|
||||
|
||||
if (Thread != null)
|
||||
{
|
||||
Threads.Remove(Thread);
|
||||
}
|
||||
|
||||
return Thread;
|
||||
}
|
||||
|
||||
private void AcquireMutexValue(long MutexAddress)
|
||||
{
|
||||
while (!Process.Memory.AcquireAddress(MutexAddress))
|
||||
{
|
||||
Thread.Yield();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseMutexValue(long MutexAddress)
|
||||
{
|
||||
Process.Memory.ReleaseAddress(MutexAddress);
|
||||
}
|
||||
|
||||
private bool IsPointingInsideKernel(long Address)
|
||||
{
|
||||
return ((ulong)Address + 0x1000000000) < 0xffffff000;
|
||||
}
|
||||
|
||||
private bool IsWordAddressUnaligned(long Address)
|
||||
{
|
||||
return (Address & 3) != 0;
|
||||
}
|
||||
}
|
||||
}
|
12
Ryujinx.HLE/OsHle/MemoryAllocator.cs
Normal file
12
Ryujinx.HLE/OsHle/MemoryAllocator.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
class MemoryAllocator
|
||||
{
|
||||
public bool TryAllocate(long Size, out long Address)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
29
Ryujinx.HLE/OsHle/MemoryRegions.cs
Normal file
29
Ryujinx.HLE/OsHle/MemoryRegions.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using ChocolArm64.Memory;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
static class MemoryRegions
|
||||
{
|
||||
public const long AddrSpaceStart = 0x08000000;
|
||||
|
||||
public const long MapRegionAddress = 0x10000000;
|
||||
public const long MapRegionSize = 0x20000000;
|
||||
|
||||
public const long HeapRegionAddress = MapRegionAddress + MapRegionSize;
|
||||
public const long HeapRegionSize = TlsPagesAddress - HeapRegionAddress;
|
||||
|
||||
public const long MainStackSize = 0x100000;
|
||||
|
||||
public const long MainStackAddress = AMemoryMgr.AddrSize - MainStackSize;
|
||||
|
||||
public const long TlsPagesSize = 0x20000;
|
||||
|
||||
public const long TlsPagesAddress = MainStackAddress - TlsPagesSize;
|
||||
|
||||
public const long TotalMemoryUsed = HeapRegionAddress + TlsPagesSize + MainStackSize;
|
||||
|
||||
public const long TotalMemoryAvailable = AMemoryMgr.RamSize - AddrSpaceStart;
|
||||
|
||||
public const long AddrSpaceSize = AMemoryMgr.AddrSize - AddrSpaceStart;
|
||||
}
|
||||
}
|
25
Ryujinx.HLE/OsHle/MemoryType.cs
Normal file
25
Ryujinx.HLE/OsHle/MemoryType.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
enum MemoryType
|
||||
{
|
||||
Unmapped = 0,
|
||||
Io = 1,
|
||||
Normal = 2,
|
||||
CodeStatic = 3,
|
||||
CodeMutable = 4,
|
||||
Heap = 5,
|
||||
SharedMemory = 6,
|
||||
ModCodeStatic = 8,
|
||||
ModCodeMutable = 9,
|
||||
IpcBuffer0 = 10,
|
||||
MappedMemory = 11,
|
||||
ThreadLocal = 12,
|
||||
TransferMemoryIsolated = 13,
|
||||
TransferMemory = 14,
|
||||
ProcessMemory = 15,
|
||||
Reserved = 16,
|
||||
IpcBuffer1 = 17,
|
||||
IpcBuffer3 = 18,
|
||||
KernelStack = 19
|
||||
}
|
||||
}
|
436
Ryujinx.HLE/OsHle/Process.cs
Normal file
436
Ryujinx.HLE/OsHle/Process.cs
Normal file
|
@ -0,0 +1,436 @@
|
|||
using ChocolArm64;
|
||||
using ChocolArm64.Events;
|
||||
using ChocolArm64.Memory;
|
||||
using ChocolArm64.State;
|
||||
using Ryujinx.HLE.Loaders;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Diagnostics;
|
||||
using Ryujinx.HLE.OsHle.Exceptions;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using Ryujinx.HLE.OsHle.Kernel;
|
||||
using Ryujinx.HLE.OsHle.Services.Nv;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
class Process : IDisposable
|
||||
{
|
||||
private const int TlsSize = 0x200;
|
||||
|
||||
private const int TotalTlsSlots = (int)MemoryRegions.TlsPagesSize / TlsSize;
|
||||
|
||||
private const int TickFreq = 19_200_000;
|
||||
|
||||
private Switch Ns;
|
||||
|
||||
public bool NeedsHbAbi { get; private set; }
|
||||
|
||||
public long HbAbiDataPosition { get; private set; }
|
||||
|
||||
public int ProcessId { get; private set; }
|
||||
|
||||
private ATranslator Translator;
|
||||
|
||||
public AMemory Memory { get; private set; }
|
||||
|
||||
public KProcessScheduler Scheduler { get; private set; }
|
||||
|
||||
public List<KThread> ThreadArbiterList { get; private set; }
|
||||
|
||||
public object ThreadSyncLock { get; private set; }
|
||||
|
||||
public KProcessHandleTable HandleTable { get; private set; }
|
||||
|
||||
public AppletStateMgr AppletState { get; private set; }
|
||||
|
||||
private SvcHandler SvcHandler;
|
||||
|
||||
private ConcurrentDictionary<int, AThread> TlsSlots;
|
||||
|
||||
private ConcurrentDictionary<long, KThread> Threads;
|
||||
|
||||
private KThread MainThread;
|
||||
|
||||
private List<Executable> Executables;
|
||||
|
||||
private Dictionary<long, string> SymbolTable;
|
||||
|
||||
private long ImageBase;
|
||||
|
||||
private bool ShouldDispose;
|
||||
|
||||
private bool Disposed;
|
||||
|
||||
public Process(Switch Ns, KProcessScheduler Scheduler, int ProcessId)
|
||||
{
|
||||
this.Ns = Ns;
|
||||
this.Scheduler = Scheduler;
|
||||
this.ProcessId = ProcessId;
|
||||
|
||||
Memory = new AMemory();
|
||||
|
||||
ThreadArbiterList = new List<KThread>();
|
||||
|
||||
ThreadSyncLock = new object();
|
||||
|
||||
HandleTable = new KProcessHandleTable();
|
||||
|
||||
AppletState = new AppletStateMgr();
|
||||
|
||||
SvcHandler = new SvcHandler(Ns, this);
|
||||
|
||||
TlsSlots = new ConcurrentDictionary<int, AThread>();
|
||||
|
||||
Threads = new ConcurrentDictionary<long, KThread>();
|
||||
|
||||
Executables = new List<Executable>();
|
||||
|
||||
ImageBase = MemoryRegions.AddrSpaceStart;
|
||||
|
||||
MapRWMemRegion(
|
||||
MemoryRegions.TlsPagesAddress,
|
||||
MemoryRegions.TlsPagesSize,
|
||||
MemoryType.ThreadLocal);
|
||||
}
|
||||
|
||||
public void LoadProgram(IExecutable Program)
|
||||
{
|
||||
if (Disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Process));
|
||||
}
|
||||
|
||||
Ns.Log.PrintInfo(LogClass.Loader, $"Image base at 0x{ImageBase:x16}.");
|
||||
|
||||
Executable Executable = new Executable(Program, Memory, ImageBase);
|
||||
|
||||
Executables.Add(Executable);
|
||||
|
||||
ImageBase = AMemoryHelper.PageRoundUp(Executable.ImageEnd);
|
||||
}
|
||||
|
||||
public void SetEmptyArgs()
|
||||
{
|
||||
//TODO: This should be part of Run.
|
||||
ImageBase += AMemoryMgr.PageSize;
|
||||
}
|
||||
|
||||
public bool Run(bool NeedsHbAbi = false)
|
||||
{
|
||||
if (Disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Process));
|
||||
}
|
||||
|
||||
this.NeedsHbAbi = NeedsHbAbi;
|
||||
|
||||
if (Executables.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MakeSymbolTable();
|
||||
|
||||
MapRWMemRegion(
|
||||
MemoryRegions.MainStackAddress,
|
||||
MemoryRegions.MainStackSize,
|
||||
MemoryType.Normal);
|
||||
|
||||
long StackTop = MemoryRegions.MainStackAddress + MemoryRegions.MainStackSize;
|
||||
|
||||
int Handle = MakeThread(Executables[0].ImageBase, StackTop, 0, 44, 0);
|
||||
|
||||
if (Handle == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MainThread = HandleTable.GetData<KThread>(Handle);
|
||||
|
||||
if (NeedsHbAbi)
|
||||
{
|
||||
HbAbiDataPosition = AMemoryHelper.PageRoundUp(Executables[0].ImageEnd);
|
||||
|
||||
Homebrew.WriteHbAbiData(Memory, HbAbiDataPosition, Handle);
|
||||
|
||||
MainThread.Thread.ThreadState.X0 = (ulong)HbAbiDataPosition;
|
||||
MainThread.Thread.ThreadState.X1 = ulong.MaxValue;
|
||||
}
|
||||
|
||||
Scheduler.StartThread(MainThread);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MapRWMemRegion(long Position, long Size, MemoryType Type)
|
||||
{
|
||||
Memory.Manager.Map(Position, Size, (int)Type, AMemoryPerm.RW);
|
||||
}
|
||||
|
||||
public void StopAllThreadsAsync()
|
||||
{
|
||||
if (Disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Process));
|
||||
}
|
||||
|
||||
if (MainThread != null)
|
||||
{
|
||||
MainThread.Thread.StopExecution();
|
||||
}
|
||||
|
||||
foreach (AThread Thread in TlsSlots.Values)
|
||||
{
|
||||
Thread.StopExecution();
|
||||
}
|
||||
}
|
||||
|
||||
public int MakeThread(
|
||||
long EntryPoint,
|
||||
long StackTop,
|
||||
long ArgsPtr,
|
||||
int Priority,
|
||||
int ProcessorId)
|
||||
{
|
||||
if (Disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Process));
|
||||
}
|
||||
|
||||
AThread CpuThread = new AThread(GetTranslator(), Memory, EntryPoint);
|
||||
|
||||
KThread Thread = new KThread(CpuThread, this, ProcessorId, Priority);
|
||||
|
||||
int Handle = HandleTable.OpenHandle(Thread);
|
||||
|
||||
int ThreadId = GetFreeTlsSlot(CpuThread);
|
||||
|
||||
long Tpidr = MemoryRegions.TlsPagesAddress + ThreadId * TlsSize;
|
||||
|
||||
CpuThread.ThreadState.ProcessId = ProcessId;
|
||||
CpuThread.ThreadState.ThreadId = ThreadId;
|
||||
CpuThread.ThreadState.CntfrqEl0 = TickFreq;
|
||||
CpuThread.ThreadState.Tpidr = Tpidr;
|
||||
|
||||
CpuThread.ThreadState.X0 = (ulong)ArgsPtr;
|
||||
CpuThread.ThreadState.X1 = (ulong)Handle;
|
||||
CpuThread.ThreadState.X31 = (ulong)StackTop;
|
||||
|
||||
CpuThread.ThreadState.Break += BreakHandler;
|
||||
CpuThread.ThreadState.SvcCall += SvcHandler.SvcCall;
|
||||
CpuThread.ThreadState.Undefined += UndefinedHandler;
|
||||
|
||||
CpuThread.WorkFinished += ThreadFinished;
|
||||
|
||||
Threads.TryAdd(CpuThread.ThreadState.Tpidr, Thread);
|
||||
|
||||
return Handle;
|
||||
}
|
||||
|
||||
private void BreakHandler(object sender, AInstExceptionEventArgs e)
|
||||
{
|
||||
throw new GuestBrokeExecutionException();
|
||||
}
|
||||
|
||||
private void UndefinedHandler(object sender, AInstUndefinedEventArgs e)
|
||||
{
|
||||
throw new UndefinedInstructionException(e.Position, e.RawOpCode);
|
||||
}
|
||||
|
||||
private void MakeSymbolTable()
|
||||
{
|
||||
SymbolTable = new Dictionary<long, string>();
|
||||
|
||||
foreach (Executable Exe in Executables)
|
||||
{
|
||||
foreach (KeyValuePair<long, string> KV in Exe.SymbolTable)
|
||||
{
|
||||
SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ATranslator GetTranslator()
|
||||
{
|
||||
if (Translator == null)
|
||||
{
|
||||
Translator = new ATranslator(SymbolTable);
|
||||
|
||||
Translator.CpuTrace += CpuTraceHandler;
|
||||
}
|
||||
|
||||
return Translator;
|
||||
}
|
||||
|
||||
public void EnableCpuTracing()
|
||||
{
|
||||
Translator.EnableCpuTrace = true;
|
||||
}
|
||||
|
||||
public void DisableCpuTracing()
|
||||
{
|
||||
Translator.EnableCpuTrace = false;
|
||||
}
|
||||
|
||||
private void CpuTraceHandler(object sender, ACpuTraceEventArgs e)
|
||||
{
|
||||
string NsoName = string.Empty;
|
||||
|
||||
for (int Index = Executables.Count - 1; Index >= 0; Index--)
|
||||
{
|
||||
if (e.Position >= Executables[Index].ImageBase)
|
||||
{
|
||||
NsoName = $"{(e.Position - Executables[Index].ImageBase):x16}";
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ns.Log.PrintDebug(LogClass.Cpu, $"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}");
|
||||
}
|
||||
|
||||
public void PrintStackTrace(AThreadState ThreadState)
|
||||
{
|
||||
long[] Positions = ThreadState.GetCallStack();
|
||||
|
||||
StringBuilder Trace = new StringBuilder();
|
||||
|
||||
Trace.AppendLine("Guest stack trace:");
|
||||
|
||||
foreach (long Position in Positions)
|
||||
{
|
||||
if (!SymbolTable.TryGetValue(Position, out string SubName))
|
||||
{
|
||||
SubName = $"Sub{Position:x16}";
|
||||
}
|
||||
else if (SubName.StartsWith("_Z"))
|
||||
{
|
||||
SubName = Demangler.Parse(SubName);
|
||||
}
|
||||
|
||||
Trace.AppendLine(" " + SubName + " (" + GetNsoNameAndAddress(Position) + ")");
|
||||
}
|
||||
|
||||
Ns.Log.PrintInfo(LogClass.Cpu, Trace.ToString());
|
||||
}
|
||||
|
||||
private string GetNsoNameAndAddress(long Position)
|
||||
{
|
||||
string Name = string.Empty;
|
||||
|
||||
for (int Index = Executables.Count - 1; Index >= 0; Index--)
|
||||
{
|
||||
if (Position >= Executables[Index].ImageBase)
|
||||
{
|
||||
long Offset = Position - Executables[Index].ImageBase;
|
||||
|
||||
Name = $"{Executables[Index].Name}:{Offset:x8}";
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Name;
|
||||
}
|
||||
|
||||
private int GetFreeTlsSlot(AThread Thread)
|
||||
{
|
||||
for (int Index = 1; Index < TotalTlsSlots; Index++)
|
||||
{
|
||||
if (TlsSlots.TryAdd(Index, Thread))
|
||||
{
|
||||
return Index;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private void ThreadFinished(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is AThread Thread)
|
||||
{
|
||||
TlsSlots.TryRemove(GetTlsSlot(Thread.ThreadState.Tpidr), out _);
|
||||
|
||||
Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread);
|
||||
|
||||
Scheduler.RemoveThread(KernelThread);
|
||||
|
||||
KernelThread.WaitEvent.Set();
|
||||
}
|
||||
|
||||
if (TlsSlots.Count == 0)
|
||||
{
|
||||
if (ShouldDispose)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
Ns.Os.ExitProcess(ProcessId);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetTlsSlot(long Position)
|
||||
{
|
||||
return (int)((Position - MemoryRegions.TlsPagesAddress) / TlsSize);
|
||||
}
|
||||
|
||||
public KThread GetThread(long Tpidr)
|
||||
{
|
||||
if (!Threads.TryGetValue(Tpidr, out KThread Thread))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Thread;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool Disposing)
|
||||
{
|
||||
if (Disposing && !Disposed)
|
||||
{
|
||||
//If there is still some thread running, disposing the objects is not
|
||||
//safe as the thread may try to access those resources. Instead, we set
|
||||
//the flag to have the Process disposed when all threads finishes.
|
||||
//Note: This may not happen if the guest code gets stuck on a infinite loop.
|
||||
if (TlsSlots.Count > 0)
|
||||
{
|
||||
ShouldDispose = true;
|
||||
|
||||
Ns.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} waiting all threads terminate...");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Disposed = true;
|
||||
|
||||
foreach (object Obj in HandleTable.Clear())
|
||||
{
|
||||
if (Obj is KSession Session)
|
||||
{
|
||||
Session.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
INvDrvServices.UnloadProcess(this);
|
||||
|
||||
AppletState.Dispose();
|
||||
|
||||
SvcHandler.Dispose();
|
||||
|
||||
Memory.Dispose();
|
||||
|
||||
Ns.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} exiting...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
Ryujinx.HLE/OsHle/ServiceCtx.cs
Normal file
39
Ryujinx.HLE/OsHle/ServiceCtx.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using ChocolArm64.Memory;
|
||||
using Ryujinx.HLE.OsHle.Handles;
|
||||
using Ryujinx.HLE.OsHle.Ipc;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle
|
||||
{
|
||||
class ServiceCtx
|
||||
{
|
||||
public Switch Ns { get; private set; }
|
||||
public Process Process { get; private set; }
|
||||
public AMemory Memory { get; private set; }
|
||||
public KSession Session { get; private set; }
|
||||
public IpcMessage Request { get; private set; }
|
||||
public IpcMessage Response { get; private set; }
|
||||
public BinaryReader RequestData { get; private set; }
|
||||
public BinaryWriter ResponseData { get; private set; }
|
||||
|
||||
public ServiceCtx(
|
||||
Switch Ns,
|
||||
Process Process,
|
||||
AMemory Memory,
|
||||
KSession Session,
|
||||
IpcMessage Request,
|
||||
IpcMessage Response,
|
||||
BinaryReader RequestData,
|
||||
BinaryWriter ResponseData)
|
||||
{
|
||||
this.Ns = Ns;
|
||||
this.Process = Process;
|
||||
this.Memory = Memory;
|
||||
this.Session = Session;
|
||||
this.Request = Request;
|
||||
this.Response = Response;
|
||||
this.RequestData = RequestData;
|
||||
this.ResponseData = ResponseData;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Ipc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Services.Acc
|
||||
{
|
||||
class IAccountServiceForApplication : IpcService
|
||||
{
|
||||
private Dictionary<int, ServiceProcessRequest> m_Commands;
|
||||
|
||||
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
|
||||
|
||||
public IAccountServiceForApplication()
|
||||
{
|
||||
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
||||
{
|
||||
{ 0, GetUserCount },
|
||||
{ 1, GetUserExistence },
|
||||
{ 2, ListAllUsers },
|
||||
{ 3, ListOpenUsers },
|
||||
{ 4, GetLastOpenedUser },
|
||||
{ 5, GetProfile },
|
||||
{ 100, InitializeApplicationInfo },
|
||||
{ 101, GetBaasAccountManagerForApplication }
|
||||
};
|
||||
}
|
||||
|
||||
public long GetUserCount(ServiceCtx Context)
|
||||
{
|
||||
Context.ResponseData.Write(0);
|
||||
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long GetUserExistence(ServiceCtx Context)
|
||||
{
|
||||
Context.ResponseData.Write(1);
|
||||
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long ListAllUsers(ServiceCtx Context)
|
||||
{
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long ListOpenUsers(ServiceCtx Context)
|
||||
{
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long GetLastOpenedUser(ServiceCtx Context)
|
||||
{
|
||||
Context.ResponseData.Write(0L);
|
||||
Context.ResponseData.Write(0L);
|
||||
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long GetProfile(ServiceCtx Context)
|
||||
{
|
||||
MakeObject(Context, new IProfile());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long InitializeApplicationInfo(ServiceCtx Context)
|
||||
{
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long GetBaasAccountManagerForApplication(ServiceCtx Context)
|
||||
{
|
||||
MakeObject(Context, new IManagerForApplication());
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
38
Ryujinx.HLE/OsHle/Services/Acc/IManagerForApplication.cs
Normal file
38
Ryujinx.HLE/OsHle/Services/Acc/IManagerForApplication.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Ipc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Services.Acc
|
||||
{
|
||||
class IManagerForApplication : IpcService
|
||||
{
|
||||
private Dictionary<int, ServiceProcessRequest> m_Commands;
|
||||
|
||||
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
|
||||
|
||||
public IManagerForApplication()
|
||||
{
|
||||
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
||||
{
|
||||
{ 0, CheckAvailability },
|
||||
{ 1, GetAccountId }
|
||||
};
|
||||
}
|
||||
|
||||
public long CheckAvailability(ServiceCtx Context)
|
||||
{
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long GetAccountId(ServiceCtx Context)
|
||||
{
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
Context.ResponseData.Write(0xcafeL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
36
Ryujinx.HLE/OsHle/Services/Acc/IProfile.cs
Normal file
36
Ryujinx.HLE/OsHle/Services/Acc/IProfile.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using Ryujinx.HLE.Logging;
|
||||
using Ryujinx.HLE.OsHle.Ipc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Services.Acc
|
||||
{
|
||||
class IProfile : IpcService
|
||||
{
|
||||
private Dictionary<int, ServiceProcessRequest> m_Commands;
|
||||
|
||||
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
|
||||
|
||||
public IProfile()
|
||||
{
|
||||
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
||||
{
|
||||
{ 1, GetBase }
|
||||
};
|
||||
}
|
||||
|
||||
public long GetBase(ServiceCtx Context)
|
||||
{
|
||||
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
|
||||
|
||||
Context.ResponseData.Write(0L);
|
||||
Context.ResponseData.Write(0L);
|
||||
Context.ResponseData.Write(0L);
|
||||
Context.ResponseData.Write(0L);
|
||||
Context.ResponseData.Write(0L);
|
||||
Context.ResponseData.Write(0L);
|
||||
Context.ResponseData.Write(0L);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
7
Ryujinx.HLE/OsHle/Services/Am/AmErr.cs
Normal file
7
Ryujinx.HLE/OsHle/Services/Am/AmErr.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ryujinx.HLE.OsHle.Services.Am
|
||||
{
|
||||
static class AmErr
|
||||
{
|
||||
public const int NoMessages = 3;
|
||||
}
|
||||
}
|
8
Ryujinx.HLE/OsHle/Services/Am/FocusState.cs
Normal file
8
Ryujinx.HLE/OsHle/Services/Am/FocusState.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.OsHle.Services.Am
|
||||
{
|
||||
enum FocusState
|
||||
{
|
||||
InFocus = 1,
|
||||
OutOfFocus = 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using Ryujinx.HLE.OsHle.Ipc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.OsHle.Services.Am
|
||||
{
|
||||
class IAllSystemAppletProxiesService : IpcService
|
||||
{
|
||||
private Dictionary<int, ServiceProcessRequest> m_Commands;
|
||||
|
||||
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
|
||||
|
||||
public IAllSystemAppletProxiesService()
|
||||
{
|
||||
m_Commands = new Dictionary<int, ServiceProcessRequest>()
|
||||
{
|
||||
{ 100, OpenSystemAppletProxy }
|
||||
};
|
||||
}
|
||||
|
||||
public long OpenSystemAppletProxy(ServiceCtx Context)
|
||||
{
|
||||
MakeObject(Context, new ISystemAppletProxy());
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue