Merge branch 'master' into ICommonStateGetter

This commit is contained in:
Lordmau5 2018-06-11 06:03:37 +02:00
commit c636c74dd2
248 changed files with 2266 additions and 2244 deletions

View 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;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.Gpu
{
interface INvGpuEngine
{
int[] Registers { get; }
void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry);
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.Gpu
{
interface ISwizzle
{
int GetSwizzleOffset(int X, int Y);
}
}

View 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;
}
}
}

View 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
View 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();
}
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.Gpu
{
enum NvGpuBufferType
{
Index,
Vertex,
Texture
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.Gpu
{
enum NvGpuEngine
{
_2d = 0x902d,
_3d = 0xb197,
Compute = 0xb1c0,
Kepler = 0xa140,
Dma = 0xb0b5
}
}

View 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;
}
}
}

View 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
}
}

View 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);
}
}
}

View 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
}
}

View 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]);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.Gpu
{
enum NvGpuFifoMeth
{
BindChannel = 0,
SetMacroUploadAddress = 0x45,
SendMacroCodeData = 0x46,
SetMacroBindingIndex = 0x47,
BindMacro = 0x48
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.HLE.Gpu
{
delegate void NvGpuMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry);
}

View 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;
}
}
}

View 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
View 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);
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.Gpu
{
enum TextureSwizzle
{
_1dBuffer = 0,
PitchColorKey = 1,
Pitch = 2,
BlockLinear = 3,
BlockLinearColorKey = 4
}
}

View 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
View 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);
}
}
}

View 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)
}
}

View file

@ -0,0 +1,10 @@
using System;
namespace Ryujinx.HLE.Input
{
[Flags]
public enum HidControllerColorDesc
{
ColorDesc_ColorsNonexistent = (1 << 1)
}
}

View 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)
}
}

View 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
}
}

View 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
}
}

View 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)
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.Input
{
public struct HidJoystickPosition
{
public int DX;
public int DY;
}
}

View 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
View 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;
}
}

View 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
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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
}
}

View 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;
}
}
}

View 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
}
}

View 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;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.Loaders
{
enum ElfSymBinding
{
STB_LOCAL = 0,
STB_GLOBAL = 1,
STB_WEAK = 2
}
}

View 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
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.Loaders
{
enum ElfSymVisibility
{
STV_DEFAULT = 0,
STV_INTERNAL = 1,
STV_HIDDEN = 2,
STV_PROTECTED = 3
}
}

View 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;
}
}
}

View 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; }
}
}

View 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);
}
}
}

View 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();
}
}
}
}

View 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
}
}

View 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;
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.Logging
{
public enum LogLevel
{
Debug,
Stub,
Info,
Warning,
Error
}
}

View 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}";
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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
}
}

View 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) { }
}
}

View file

@ -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)) { }
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.HLE.OsHle.Handles
{
class KEvent : KSynchronizationObject { }
}

View 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();
}
}
}

View 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();
}
}
}
}
}

View 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();
}
}
}
}

View 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();
}
}
}
}

View 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();
}
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}

View 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;
}
}

View 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);
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.OsHle.Ipc
{
enum IpcMessageType
{
Response = 0,
CloseSession = 2,
Request = 4,
Control = 5
}
}

View 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);
}
}
}

View 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);
}
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.HLE.OsHle.Ipc
{
delegate long ServiceProcessRequest(ServiceCtx Context);
}

View 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;
}
}

View 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;
}
}
}
}

View 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();
}
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.HLE.OsHle
{
class MemoryAllocator
{
public bool TryAllocate(long Size, out long Address)
{
throw new NotImplementedException();
}
}
}

View 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;
}
}

View 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
}
}

View 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...");
}
}
}
}

View 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;
}
}
}

View file

@ -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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.OsHle.Services.Am
{
static class AmErr
{
public const int NoMessages = 3;
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.OsHle.Services.Am
{
enum FocusState
{
InFocus = 1,
OutOfFocus = 2
}
}

View file

@ -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