diff options
Diffstat (limited to 'Ryujinx.HLE')
236 files changed, 19226 insertions, 0 deletions
diff --git a/Ryujinx.HLE/Gpu/BlockLinearSwizzle.cs b/Ryujinx.HLE/Gpu/BlockLinearSwizzle.cs new file mode 100644 index 00000000..366f5740 --- /dev/null +++ b/Ryujinx.HLE/Gpu/BlockLinearSwizzle.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/INvGpuEngine.cs b/Ryujinx.HLE/Gpu/INvGpuEngine.cs new file mode 100644 index 00000000..62307f58 --- /dev/null +++ b/Ryujinx.HLE/Gpu/INvGpuEngine.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.Gpu +{ + interface INvGpuEngine + { + int[] Registers { get; } + + void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry); + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/ISwizzle.cs b/Ryujinx.HLE/Gpu/ISwizzle.cs new file mode 100644 index 00000000..525707ba --- /dev/null +++ b/Ryujinx.HLE/Gpu/ISwizzle.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.Gpu +{ + interface ISwizzle + { + int GetSwizzleOffset(int X, int Y); + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/LinearSwizzle.cs b/Ryujinx.HLE/Gpu/LinearSwizzle.cs new file mode 100644 index 00000000..995866ad --- /dev/null +++ b/Ryujinx.HLE/Gpu/LinearSwizzle.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/MacroInterpreter.cs b/Ryujinx.HLE/Gpu/MacroInterpreter.cs new file mode 100644 index 00000000..58a236ad --- /dev/null +++ b/Ryujinx.HLE/Gpu/MacroInterpreter.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpu.cs b/Ryujinx.HLE/Gpu/NvGpu.cs new file mode 100644 index 00000000..1e433fa4 --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpu.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuBufferType.cs b/Ryujinx.HLE/Gpu/NvGpuBufferType.cs new file mode 100644 index 00000000..a44a772d --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuBufferType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.Gpu +{ + enum NvGpuBufferType + { + Index, + Vertex, + Texture + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuEngine.cs b/Ryujinx.HLE/Gpu/NvGpuEngine.cs new file mode 100644 index 00000000..41697ed6 --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuEngine.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.Gpu +{ + enum NvGpuEngine + { + _2d = 0x902d, + _3d = 0xb197, + Compute = 0xb1c0, + Kepler = 0xa140, + Dma = 0xb0b5 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuEngine2d.cs b/Ryujinx.HLE/Gpu/NvGpuEngine2d.cs new file mode 100644 index 00000000..15667eeb --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuEngine2d.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuEngine2dReg.cs b/Ryujinx.HLE/Gpu/NvGpuEngine2dReg.cs new file mode 100644 index 00000000..1039e368 --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuEngine2dReg.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/NvGpuEngine3d.cs new file mode 100644 index 00000000..b3f1330b --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuEngine3d.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuEngine3dReg.cs b/Ryujinx.HLE/Gpu/NvGpuEngine3dReg.cs new file mode 100644 index 00000000..e0de4777 --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuEngine3dReg.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuFifo.cs b/Ryujinx.HLE/Gpu/NvGpuFifo.cs new file mode 100644 index 00000000..0df37edc --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuFifo.cs @@ -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]); + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuFifoMeth.cs b/Ryujinx.HLE/Gpu/NvGpuFifoMeth.cs new file mode 100644 index 00000000..247a7bfc --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuFifoMeth.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.Gpu +{ + enum NvGpuFifoMeth + { + BindChannel = 0, + SetMacroUploadAddress = 0x45, + SendMacroCodeData = 0x46, + SetMacroBindingIndex = 0x47, + BindMacro = 0x48 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuMethod.cs b/Ryujinx.HLE/Gpu/NvGpuMethod.cs new file mode 100644 index 00000000..f7ff6647 --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuMethod.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.HLE.Gpu +{ + delegate void NvGpuMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry); +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuPBEntry.cs b/Ryujinx.HLE/Gpu/NvGpuPBEntry.cs new file mode 100644 index 00000000..2cd663fe --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuPBEntry.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuPushBuffer.cs b/Ryujinx.HLE/Gpu/NvGpuPushBuffer.cs new file mode 100644 index 00000000..2d4f0c1a --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuPushBuffer.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuVmm.cs b/Ryujinx.HLE/Gpu/NvGpuVmm.cs new file mode 100644 index 00000000..b0ba3e90 --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuVmm.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/NvGpuVmmCache.cs b/Ryujinx.HLE/Gpu/NvGpuVmmCache.cs new file mode 100644 index 00000000..38b25e4f --- /dev/null +++ b/Ryujinx.HLE/Gpu/NvGpuVmmCache.cs @@ -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; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/Texture.cs b/Ryujinx.HLE/Gpu/Texture.cs new file mode 100644 index 00000000..1de7f302 --- /dev/null +++ b/Ryujinx.HLE/Gpu/Texture.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/TextureFactory.cs b/Ryujinx.HLE/Gpu/TextureFactory.cs new file mode 100644 index 00000000..9a92a016 --- /dev/null +++ b/Ryujinx.HLE/Gpu/TextureFactory.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/TextureHelper.cs b/Ryujinx.HLE/Gpu/TextureHelper.cs new file mode 100644 index 00000000..f1b3f0b3 --- /dev/null +++ b/Ryujinx.HLE/Gpu/TextureHelper.cs @@ -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); + } + } +} diff --git a/Ryujinx.HLE/Gpu/TextureReader.cs b/Ryujinx.HLE/Gpu/TextureReader.cs new file mode 100644 index 00000000..4436e07f --- /dev/null +++ b/Ryujinx.HLE/Gpu/TextureReader.cs @@ -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; + } + } +} diff --git a/Ryujinx.HLE/Gpu/TextureSwizzle.cs b/Ryujinx.HLE/Gpu/TextureSwizzle.cs new file mode 100644 index 00000000..5e32f4c7 --- /dev/null +++ b/Ryujinx.HLE/Gpu/TextureSwizzle.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.Gpu +{ + enum TextureSwizzle + { + _1dBuffer = 0, + PitchColorKey = 1, + Pitch = 2, + BlockLinear = 3, + BlockLinearColorKey = 4 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Gpu/TextureWriter.cs b/Ryujinx.HLE/Gpu/TextureWriter.cs new file mode 100644 index 00000000..ad92961c --- /dev/null +++ b/Ryujinx.HLE/Gpu/TextureWriter.cs @@ -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; + } + } + } + } +} diff --git a/Ryujinx.HLE/Hid/Hid.cs b/Ryujinx.HLE/Hid/Hid.cs new file mode 100644 index 00000000..054c4fac --- /dev/null +++ b/Ryujinx.HLE/Hid/Hid.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerButtons.cs b/Ryujinx.HLE/Hid/HidControllerButtons.cs new file mode 100644 index 00000000..f41d17e1 --- /dev/null +++ b/Ryujinx.HLE/Hid/HidControllerButtons.cs @@ -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) + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerColorDesc.cs b/Ryujinx.HLE/Hid/HidControllerColorDesc.cs new file mode 100644 index 00000000..b8cf2a5e --- /dev/null +++ b/Ryujinx.HLE/Hid/HidControllerColorDesc.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum HidControllerColorDesc + { + ColorDesc_ColorsNonexistent = (1 << 1) + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerConnState.cs b/Ryujinx.HLE/Hid/HidControllerConnState.cs new file mode 100644 index 00000000..1fc9482a --- /dev/null +++ b/Ryujinx.HLE/Hid/HidControllerConnState.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.Input +{ + [Flags] + public enum HidControllerConnState + { + Controller_State_Connected = (1 << 0), + Controller_State_Wired = (1 << 1) + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerId.cs b/Ryujinx.HLE/Hid/HidControllerId.cs new file mode 100644 index 00000000..e4a0e26c --- /dev/null +++ b/Ryujinx.HLE/Hid/HidControllerId.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerLayouts.cs b/Ryujinx.HLE/Hid/HidControllerLayouts.cs new file mode 100644 index 00000000..39fdd3fe --- /dev/null +++ b/Ryujinx.HLE/Hid/HidControllerLayouts.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidControllerType.cs b/Ryujinx.HLE/Hid/HidControllerType.cs new file mode 100644 index 00000000..ea8ddfd4 --- /dev/null +++ b/Ryujinx.HLE/Hid/HidControllerType.cs @@ -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) + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidJoystickPosition.cs b/Ryujinx.HLE/Hid/HidJoystickPosition.cs new file mode 100644 index 00000000..a06ef7b2 --- /dev/null +++ b/Ryujinx.HLE/Hid/HidJoystickPosition.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.Input +{ + public struct HidJoystickPosition + { + public int DX; + public int DY; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/HidTouchPoint.cs b/Ryujinx.HLE/Hid/HidTouchPoint.cs new file mode 100644 index 00000000..25412456 --- /dev/null +++ b/Ryujinx.HLE/Hid/HidTouchPoint.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Hid/JoyCon.cs b/Ryujinx.HLE/Hid/JoyCon.cs new file mode 100644 index 00000000..e45e1a47 --- /dev/null +++ b/Ryujinx.HLE/Hid/JoyCon.cs @@ -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; + } +} diff --git a/Ryujinx.HLE/Hid/JoyConColor.cs b/Ryujinx.HLE/Hid/JoyConColor.cs new file mode 100644 index 00000000..514ec21b --- /dev/null +++ b/Ryujinx.HLE/Hid/JoyConColor.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Compression/Lz4.cs b/Ryujinx.HLE/Loaders/Compression/Lz4.cs new file mode 100644 index 00000000..cfb49551 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Compression/Lz4.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfDyn.cs b/Ryujinx.HLE/Loaders/ElfDyn.cs new file mode 100644 index 00000000..3508e6e4 --- /dev/null +++ b/Ryujinx.HLE/Loaders/ElfDyn.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfDynTag.cs b/Ryujinx.HLE/Loaders/ElfDynTag.cs new file mode 100644 index 00000000..5915d4d1 --- /dev/null +++ b/Ryujinx.HLE/Loaders/ElfDynTag.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfRel.cs b/Ryujinx.HLE/Loaders/ElfRel.cs new file mode 100644 index 00000000..cfc31d89 --- /dev/null +++ b/Ryujinx.HLE/Loaders/ElfRel.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfRelType.cs b/Ryujinx.HLE/Loaders/ElfRelType.cs new file mode 100644 index 00000000..7da5eec3 --- /dev/null +++ b/Ryujinx.HLE/Loaders/ElfRelType.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSym.cs b/Ryujinx.HLE/Loaders/ElfSym.cs new file mode 100644 index 00000000..869938d3 --- /dev/null +++ b/Ryujinx.HLE/Loaders/ElfSym.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSymBinding.cs b/Ryujinx.HLE/Loaders/ElfSymBinding.cs new file mode 100644 index 00000000..f1d249ec --- /dev/null +++ b/Ryujinx.HLE/Loaders/ElfSymBinding.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.Loaders +{ + enum ElfSymBinding + { + STB_LOCAL = 0, + STB_GLOBAL = 1, + STB_WEAK = 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSymType.cs b/Ryujinx.HLE/Loaders/ElfSymType.cs new file mode 100644 index 00000000..478064bc --- /dev/null +++ b/Ryujinx.HLE/Loaders/ElfSymType.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSymVisibility.cs b/Ryujinx.HLE/Loaders/ElfSymVisibility.cs new file mode 100644 index 00000000..fe7243a7 --- /dev/null +++ b/Ryujinx.HLE/Loaders/ElfSymVisibility.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.Loaders +{ + enum ElfSymVisibility + { + STV_DEFAULT = 0, + STV_INTERNAL = 1, + STV_HIDDEN = 2, + STV_PROTECTED = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executable.cs b/Ryujinx.HLE/Loaders/Executable.cs new file mode 100644 index 00000000..618ee241 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Executable.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/IExecutable.cs b/Ryujinx.HLE/Loaders/Executables/IExecutable.cs new file mode 100644 index 00000000..1e8e569a --- /dev/null +++ b/Ryujinx.HLE/Loaders/Executables/IExecutable.cs @@ -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; } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/Nro.cs b/Ryujinx.HLE/Loaders/Executables/Nro.cs new file mode 100644 index 00000000..9e2b7e90 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Executables/Nro.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/Nso.cs b/Ryujinx.HLE/Loaders/Executables/Nso.cs new file mode 100644 index 00000000..a5e1a361 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Executables/Nso.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Logging/LogClass.cs b/Ryujinx.HLE/Logging/LogClass.cs new file mode 100644 index 00000000..c377ace6 --- /dev/null +++ b/Ryujinx.HLE/Logging/LogClass.cs @@ -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 + } +} diff --git a/Ryujinx.HLE/Logging/LogEventArgs.cs b/Ryujinx.HLE/Logging/LogEventArgs.cs new file mode 100644 index 00000000..647cf713 --- /dev/null +++ b/Ryujinx.HLE/Logging/LogEventArgs.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Logging/LogLevel.cs b/Ryujinx.HLE/Logging/LogLevel.cs new file mode 100644 index 00000000..971333e6 --- /dev/null +++ b/Ryujinx.HLE/Logging/LogLevel.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.Logging +{ + public enum LogLevel + { + Debug, + Stub, + Info, + Warning, + Error + } +} diff --git a/Ryujinx.HLE/Logging/Logger.cs b/Ryujinx.HLE/Logging/Logger.cs new file mode 100644 index 00000000..5376b253 --- /dev/null +++ b/Ryujinx.HLE/Logging/Logger.cs @@ -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}"; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/AppletStateMgr.cs b/Ryujinx.HLE/OsHle/AppletStateMgr.cs new file mode 100644 index 00000000..5fdb5343 --- /dev/null +++ b/Ryujinx.HLE/OsHle/AppletStateMgr.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Diagnostics/Demangler.cs b/Ryujinx.HLE/OsHle/Diagnostics/Demangler.cs new file mode 100644 index 00000000..6646dede --- /dev/null +++ b/Ryujinx.HLE/OsHle/Diagnostics/Demangler.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/ErrorCode.cs b/Ryujinx.HLE/OsHle/ErrorCode.cs new file mode 100644 index 00000000..1e07f9b2 --- /dev/null +++ b/Ryujinx.HLE/OsHle/ErrorCode.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/ErrorModule.cs b/Ryujinx.HLE/OsHle/ErrorModule.cs new file mode 100644 index 00000000..924ee951 --- /dev/null +++ b/Ryujinx.HLE/OsHle/ErrorModule.cs @@ -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 + } +} diff --git a/Ryujinx.HLE/OsHle/Exceptions/GuestBrokeExecutionException.cs b/Ryujinx.HLE/OsHle/Exceptions/GuestBrokeExecutionException.cs new file mode 100644 index 00000000..2ed7f19e --- /dev/null +++ b/Ryujinx.HLE/OsHle/Exceptions/GuestBrokeExecutionException.cs @@ -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) { } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Exceptions/UndefinedInstructionException.cs b/Ryujinx.HLE/OsHle/Exceptions/UndefinedInstructionException.cs new file mode 100644 index 00000000..d9f0b8cf --- /dev/null +++ b/Ryujinx.HLE/OsHle/Exceptions/UndefinedInstructionException.cs @@ -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)) { } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/GlobalStateTable.cs b/Ryujinx.HLE/OsHle/GlobalStateTable.cs new file mode 100644 index 00000000..fb71e46b --- /dev/null +++ b/Ryujinx.HLE/OsHle/GlobalStateTable.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs b/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs new file mode 100644 index 00000000..6426e585 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/HTransferMem.cs b/Ryujinx.HLE/OsHle/Handles/HTransferMem.cs new file mode 100644 index 00000000..2969a2ba --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/HTransferMem.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KEvent.cs b/Ryujinx.HLE/OsHle/Handles/KEvent.cs new file mode 100644 index 00000000..df5108f9 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/KEvent.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.HLE.OsHle.Handles +{ + class KEvent : KSynchronizationObject { } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KProcessHandleTable.cs b/Ryujinx.HLE/OsHle/Handles/KProcessHandleTable.cs new file mode 100644 index 00000000..d22b63c6 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/KProcessHandleTable.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KProcessScheduler.cs b/Ryujinx.HLE/OsHle/Handles/KProcessScheduler.cs new file mode 100644 index 00000000..fa414f68 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/KProcessScheduler.cs @@ -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(); + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KSession.cs b/Ryujinx.HLE/OsHle/Handles/KSession.cs new file mode 100644 index 00000000..e85de36c --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/KSession.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KSynchronizationObject.cs b/Ryujinx.HLE/OsHle/Handles/KSynchronizationObject.cs new file mode 100644 index 00000000..0e7d06f6 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/KSynchronizationObject.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/KThread.cs b/Ryujinx.HLE/OsHle/Handles/KThread.cs new file mode 100644 index 00000000..d26a52f0 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/KThread.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/SchedulerThread.cs b/Ryujinx.HLE/OsHle/Handles/SchedulerThread.cs new file mode 100644 index 00000000..5bdefe74 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/SchedulerThread.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Handles/ThreadQueue.cs b/Ryujinx.HLE/OsHle/Handles/ThreadQueue.cs new file mode 100644 index 00000000..3cb82f0d --- /dev/null +++ b/Ryujinx.HLE/OsHle/Handles/ThreadQueue.cs @@ -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; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Homebrew.cs b/Ryujinx.HLE/OsHle/Homebrew.cs new file mode 100644 index 00000000..4266c8db --- /dev/null +++ b/Ryujinx.HLE/OsHle/Homebrew.cs @@ -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; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Horizon.cs b/Ryujinx.HLE/OsHle/Horizon.cs new file mode 100644 index 00000000..a5bf0616 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Horizon.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/IdDictionary.cs b/Ryujinx.HLE/OsHle/IdDictionary.cs new file mode 100644 index 00000000..7a93f634 --- /dev/null +++ b/Ryujinx.HLE/OsHle/IdDictionary.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcBuffDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcBuffDesc.cs new file mode 100644 index 00000000..12bff0fb --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/IpcBuffDesc.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs new file mode 100644 index 00000000..953cac76 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/IpcHandleDesc.cs @@ -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(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs b/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs new file mode 100644 index 00000000..9b46cf4b --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/IpcHandler.cs @@ -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; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcMagic.cs b/Ryujinx.HLE/OsHle/Ipc/IpcMagic.cs new file mode 100644 index 00000000..c3f9655f --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/IpcMagic.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs b/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs new file mode 100644 index 00000000..4e648aec --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/IpcMessage.cs @@ -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); + } + } +} diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs b/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs new file mode 100644 index 00000000..f596fea4 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/IpcMessageType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.OsHle.Ipc +{ + enum IpcMessageType + { + Response = 0, + CloseSession = 2, + Request = 4, + Control = 5 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcPtrBuffDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcPtrBuffDesc.cs new file mode 100644 index 00000000..f5a9f651 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/IpcPtrBuffDesc.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/IpcRecvListBuffDesc.cs b/Ryujinx.HLE/OsHle/Ipc/IpcRecvListBuffDesc.cs new file mode 100644 index 00000000..59191c16 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/IpcRecvListBuffDesc.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Ipc/ServiceProcessRequest.cs b/Ryujinx.HLE/OsHle/Ipc/ServiceProcessRequest.cs new file mode 100644 index 00000000..47f72cb7 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Ipc/ServiceProcessRequest.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.HLE.OsHle.Ipc +{ + delegate long ServiceProcessRequest(ServiceCtx Context); +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs b/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs new file mode 100644 index 00000000..643e5f68 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Kernel/KernelErr.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs b/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs new file mode 100644 index 00000000..966fdaca --- /dev/null +++ b/Ryujinx.HLE/OsHle/Kernel/NsTimeConverter.cs @@ -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; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs new file mode 100644 index 00000000..f3a5fae0 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs @@ -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(); + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs new file mode 100644 index 00000000..bb73f1ea --- /dev/null +++ b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs new file mode 100644 index 00000000..a32b2d86 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs @@ -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; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs b/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs new file mode 100644 index 00000000..9dcfaaee --- /dev/null +++ b/Ryujinx.HLE/OsHle/Kernel/SvcThread.cs @@ -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); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs new file mode 100644 index 00000000..030e6e3d --- /dev/null +++ b/Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/MemoryAllocator.cs b/Ryujinx.HLE/OsHle/MemoryAllocator.cs new file mode 100644 index 00000000..8696aa4d --- /dev/null +++ b/Ryujinx.HLE/OsHle/MemoryAllocator.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.OsHle +{ + class MemoryAllocator + { + public bool TryAllocate(long Size, out long Address) + { + throw new NotImplementedException(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/MemoryRegions.cs b/Ryujinx.HLE/OsHle/MemoryRegions.cs new file mode 100644 index 00000000..198c621c --- /dev/null +++ b/Ryujinx.HLE/OsHle/MemoryRegions.cs @@ -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; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/MemoryType.cs b/Ryujinx.HLE/OsHle/MemoryType.cs new file mode 100644 index 00000000..64b07947 --- /dev/null +++ b/Ryujinx.HLE/OsHle/MemoryType.cs @@ -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 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Process.cs b/Ryujinx.HLE/OsHle/Process.cs new file mode 100644 index 00000000..f01a0927 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Process.cs @@ -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..."); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/ServiceCtx.cs b/Ryujinx.HLE/OsHle/ServiceCtx.cs new file mode 100644 index 00000000..eb9ff5fd --- /dev/null +++ b/Ryujinx.HLE/OsHle/ServiceCtx.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/OsHle/Services/Acc/IAccountServiceForApplication.cs new file mode 100644 index 00000000..470b9ccb --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Acc/IAccountServiceForApplication.cs @@ -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; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Acc/IManagerForApplication.cs b/Ryujinx.HLE/OsHle/Services/Acc/IManagerForApplication.cs new file mode 100644 index 00000000..ce3865f4 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Acc/IManagerForApplication.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Acc/IProfile.cs b/Ryujinx.HLE/OsHle/Services/Acc/IProfile.cs new file mode 100644 index 00000000..24daa3d5 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Acc/IProfile.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/AmErr.cs b/Ryujinx.HLE/OsHle/Services/Am/AmErr.cs new file mode 100644 index 00000000..66224639 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/AmErr.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.OsHle.Services.Am +{ + static class AmErr + { + public const int NoMessages = 3; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/FocusState.cs b/Ryujinx.HLE/OsHle/Services/Am/FocusState.cs new file mode 100644 index 00000000..074f159e --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/FocusState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Am +{ + enum FocusState + { + InFocus = 1, + OutOfFocus = 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IAllSystemAppletProxiesService.cs b/Ryujinx.HLE/OsHle/Services/Am/IAllSystemAppletProxiesService.cs new file mode 100644 index 00000000..8dc17cec --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IAllSystemAppletProxiesService.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IApplicationCreator.cs b/Ryujinx.HLE/OsHle/Services/Am/IApplicationCreator.cs new file mode 100644 index 00000000..6ee5b5c2 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IApplicationCreator.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IApplicationCreator : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IApplicationCreator() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IApplicationFunctions.cs b/Ryujinx.HLE/OsHle/Services/Am/IApplicationFunctions.cs new file mode 100644 index 00000000..e25b524a --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IApplicationFunctions.cs @@ -0,0 +1,117 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IApplicationFunctions : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IApplicationFunctions() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 1, PopLaunchParameter }, + { 20, EnsureSaveData }, + { 21, GetDesiredLanguage }, + { 22, SetTerminateResult }, + { 23, GetDisplayVersion }, + { 40, NotifyRunning }, + { 50, GetPseudoDeviceId }, + { 66, InitializeGamePlayRecording }, + { 67, SetGamePlayRecordingState } + }; + } + + public long PopLaunchParameter(ServiceCtx Context) + { + //Only the first 0x18 bytes of the Data seems to be actually used. + MakeObject(Context, new IStorage(StorageHelper.MakeLaunchParams())); + + return 0; + } + + public long EnsureSaveData(ServiceCtx Context) + { + long UIdLow = Context.RequestData.ReadInt64(); + long UIdHigh = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + Context.ResponseData.Write(0L); + + return 0; + } + + public long GetDesiredLanguage(ServiceCtx Context) + { + Context.ResponseData.Write(Context.Ns.Os.SystemState.DesiredLanguageCode); + + return 0; + } + + public long SetTerminateResult(ServiceCtx Context) + { + int ErrorCode = Context.RequestData.ReadInt32(); + + string Result = GetFormattedErrorCode(ErrorCode); + + Context.Ns.Log.PrintInfo(LogClass.ServiceAm, $"Result = 0x{ErrorCode:x8} ({Result})."); + + return 0; + } + + private string GetFormattedErrorCode(int ErrorCode) + { + int Module = (ErrorCode >> 0) & 0x1ff; + int Description = (ErrorCode >> 9) & 0x1fff; + + return $"{(2000 + Module):d4}-{Description:d4}"; + } + + public long GetDisplayVersion(ServiceCtx Context) + { + //FIXME: Need to check correct version on a switch. + Context.ResponseData.Write(1L); + Context.ResponseData.Write(0L); + + return 0; + } + + public long NotifyRunning(ServiceCtx Context) + { + Context.ResponseData.Write(1); + + return 0; + } + + public long GetPseudoDeviceId(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + + return 0; + } + + public long InitializeGamePlayRecording(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetGamePlayRecordingState(ServiceCtx Context) + { + int State = Context.RequestData.ReadInt32(); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxy.cs b/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxy.cs new file mode 100644 index 00000000..ec028502 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxy.cs @@ -0,0 +1,83 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IApplicationProxy : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IApplicationProxy() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetCommonStateGetter }, + { 1, GetSelfController }, + { 2, GetWindowController }, + { 3, GetAudioController }, + { 4, GetDisplayController }, + { 11, GetLibraryAppletCreator }, + { 20, GetApplicationFunctions }, + { 1000, GetDebugFunctions } + }; + } + + public long GetCommonStateGetter(ServiceCtx Context) + { + MakeObject(Context, new ICommonStateGetter()); + + return 0; + } + + public long GetSelfController(ServiceCtx Context) + { + MakeObject(Context, new ISelfController()); + + return 0; + } + + public long GetWindowController(ServiceCtx Context) + { + MakeObject(Context, new IWindowController()); + + return 0; + } + + public long GetAudioController(ServiceCtx Context) + { + MakeObject(Context, new IAudioController()); + + return 0; + } + + public long GetDisplayController(ServiceCtx Context) + { + MakeObject(Context, new IDisplayController()); + + return 0; + } + + public long GetLibraryAppletCreator(ServiceCtx Context) + { + MakeObject(Context, new ILibraryAppletCreator()); + + return 0; + } + + public long GetApplicationFunctions(ServiceCtx Context) + { + MakeObject(Context, new IApplicationFunctions()); + + return 0; + } + + public long GetDebugFunctions(ServiceCtx Context) + { + MakeObject(Context, new IDebugFunctions()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxyService.cs b/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxyService.cs new file mode 100644 index 00000000..6b39b265 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IApplicationProxyService.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IApplicationProxyService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IApplicationProxyService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, OpenApplicationProxy } + }; + } + + public long OpenApplicationProxy(ServiceCtx Context) + { + MakeObject(Context, new IApplicationProxy()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IAudioController.cs b/Ryujinx.HLE/OsHle/Services/Am/IAudioController.cs new file mode 100644 index 00000000..3cb63181 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IAudioController.cs @@ -0,0 +1,72 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IAudioController : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IAudioController() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, SetExpectedMasterVolume }, + { 1, GetMainAppletExpectedMasterVolume }, + { 2, GetLibraryAppletExpectedMasterVolume }, + { 3, ChangeMainAppletMasterVolume }, + { 4, SetTransparentVolumeRate } + }; + } + + public long SetExpectedMasterVolume(ServiceCtx Context) + { + float AppletVolume = Context.RequestData.ReadSingle(); + float LibraryAppletVolume = Context.RequestData.ReadSingle(); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long GetMainAppletExpectedMasterVolume(ServiceCtx Context) + { + Context.ResponseData.Write(1f); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long GetLibraryAppletExpectedMasterVolume(ServiceCtx Context) + { + Context.ResponseData.Write(1f); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long ChangeMainAppletMasterVolume(ServiceCtx Context) + { + float Unknown0 = Context.RequestData.ReadSingle(); + long Unknown1 = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetTransparentVolumeRate(ServiceCtx Context) + { + float Unknown0 = Context.RequestData.ReadSingle(); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Am/ICommonStateGetter.cs b/Ryujinx.HLE/OsHle/Services/Am/ICommonStateGetter.cs new file mode 100644 index 00000000..2b575cb7 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/ICommonStateGetter.cs @@ -0,0 +1,82 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +using static Ryujinx.HLE.OsHle.ErrorCode; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class ICommonStateGetter : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ICommonStateGetter() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetEventHandle }, + { 1, ReceiveMessage }, + { 5, GetOperationMode }, + { 6, GetPerformanceMode }, + { 8, GetBootMode }, + { 9, GetCurrentFocusState } + }; + } + + public long GetEventHandle(ServiceCtx Context) + { + KEvent Event = Context.Process.AppletState.MessageEvent; + + int Handle = Context.Process.HandleTable.OpenHandle(Event); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + + public long ReceiveMessage(ServiceCtx Context) + { + if (!Context.Process.AppletState.TryDequeueMessage(out MessageInfo Message)) + { + return MakeError(ErrorModule.Am, AmErr.NoMessages); + } + + Context.ResponseData.Write((int)Message); + + return 0; + } + + public long GetOperationMode(ServiceCtx Context) + { + Context.ResponseData.Write((byte)OperationMode.Handheld); + + return 0; + } + + public long GetPerformanceMode(ServiceCtx Context) + { + Context.ResponseData.Write((byte)Apm.PerformanceMode.Handheld); + + return 0; + } + + public long GetBootMode(ServiceCtx Context) + { + Context.ResponseData.Write((byte)0); //Unknown value. + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long GetCurrentFocusState(ServiceCtx Context) + { + Context.ResponseData.Write((byte)Context.Process.AppletState.FocusState); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IDebugFunctions.cs b/Ryujinx.HLE/OsHle/Services/Am/IDebugFunctions.cs new file mode 100644 index 00000000..b07c68dd --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IDebugFunctions.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IDebugFunctions : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IDebugFunctions() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IDisplayController.cs b/Ryujinx.HLE/OsHle/Services/Am/IDisplayController.cs new file mode 100644 index 00000000..8785f071 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IDisplayController.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IDisplayController : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IDisplayController() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IGlobalStateController.cs b/Ryujinx.HLE/OsHle/Services/Am/IGlobalStateController.cs new file mode 100644 index 00000000..0fbcb284 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IGlobalStateController.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IGlobalStateController : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IGlobalStateController() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IHomeMenuFunctions.cs b/Ryujinx.HLE/OsHle/Services/Am/IHomeMenuFunctions.cs new file mode 100644 index 00000000..1005fe0c --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IHomeMenuFunctions.cs @@ -0,0 +1,46 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IHomeMenuFunctions : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private KEvent ChannelEvent; + + public IHomeMenuFunctions() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 10, RequestToGetForeground }, + { 21, GetPopFromGeneralChannelEvent } + }; + + //ToDo: Signal this Event somewhere in future. + ChannelEvent = new KEvent(); + } + + public long RequestToGetForeground(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long GetPopFromGeneralChannelEvent(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(ChannelEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletAccessor.cs b/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletAccessor.cs new file mode 100644 index 00000000..b1955cc6 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletAccessor.cs @@ -0,0 +1,71 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class ILibraryAppletAccessor : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private KEvent StateChangedEvent; + + public ILibraryAppletAccessor() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetAppletStateChangedEvent }, + { 10, Start }, + { 30, GetResult }, + { 100, PushInData }, + { 101, PopOutData } + }; + + StateChangedEvent = new KEvent(); + } + + public long GetAppletStateChangedEvent(ServiceCtx Context) + { + StateChangedEvent.WaitEvent.Set(); + + int Handle = Context.Process.HandleTable.OpenHandle(StateChangedEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long Start(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long GetResult(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long PushInData(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long PopOutData(ServiceCtx Context) + { + MakeObject(Context, new IStorage(StorageHelper.MakeLaunchParams())); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletCreator.cs b/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletCreator.cs new file mode 100644 index 00000000..66973fc6 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/ILibraryAppletCreator.cs @@ -0,0 +1,37 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class ILibraryAppletCreator : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ILibraryAppletCreator() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, CreateLibraryApplet }, + { 10, CreateStorage } + }; + } + + public long CreateLibraryApplet(ServiceCtx Context) + { + MakeObject(Context, new ILibraryAppletAccessor()); + + return 0; + } + + public long CreateStorage(ServiceCtx Context) + { + long Size = Context.RequestData.ReadInt64(); + + MakeObject(Context, new IStorage(new byte[Size])); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs b/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs new file mode 100644 index 00000000..ee0fb915 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/ISelfController.cs @@ -0,0 +1,117 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class ISelfController : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private KEvent LaunchableEvent; + + public ISelfController() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 1, LockExit }, + { 9, GetLibraryAppletLaunchableEvent }, + { 10, SetScreenShotPermission }, + { 11, SetOperationModeChangedNotification }, + { 12, SetPerformanceModeChangedNotification }, + { 13, SetFocusHandlingMode }, + { 14, SetRestartMessageEnabled }, + { 16, SetOutOfFocusSuspendingEnabled }, + { 50, SetHandlesRequestToDisplay } + }; + + LaunchableEvent = new KEvent(); + } + + public long LockExit(ServiceCtx Context) + { + return 0; + } + + public long GetLibraryAppletLaunchableEvent(ServiceCtx Context) + { + LaunchableEvent.WaitEvent.Set(); + + int Handle = Context.Process.HandleTable.OpenHandle(LaunchableEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetScreenShotPermission(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetOperationModeChangedNotification(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetPerformanceModeChangedNotification(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetFocusHandlingMode(ServiceCtx Context) + { + bool Flag1 = Context.RequestData.ReadByte() != 0 ? true : false; + bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false; + bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false; + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetRestartMessageEnabled(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetOutOfFocusSuspendingEnabled(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + + public long SetHandlesRequestToDisplay(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IStorage.cs b/Ryujinx.HLE/OsHle/Services/Am/IStorage.cs new file mode 100644 index 00000000..0aa1f571 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IStorage.cs @@ -0,0 +1,31 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IStorage : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public byte[] Data { get; private set; } + + public IStorage(byte[] Data) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Open } + }; + + this.Data = Data; + } + + public long Open(ServiceCtx Context) + { + MakeObject(Context, new IStorageAccessor(this)); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IStorageAccessor.cs b/Ryujinx.HLE/OsHle/Services/Am/IStorageAccessor.cs new file mode 100644 index 00000000..c2a8c11e --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IStorageAccessor.cs @@ -0,0 +1,83 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IStorageAccessor : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private IStorage Storage; + + public IStorageAccessor(IStorage Storage) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetSize }, + { 10, Write }, + { 11, Read } + }; + + this.Storage = Storage; + } + + public long GetSize(ServiceCtx Context) + { + Context.ResponseData.Write((long)Storage.Data.Length); + + return 0; + } + + public long Write(ServiceCtx Context) + { + //TODO: Error conditions. + long WritePosition = Context.RequestData.ReadInt64(); + + (long Position, long Size) = Context.Request.GetBufferType0x21(); + + if (Size > 0) + { + long MaxSize = Storage.Data.Length - WritePosition; + + if (Size > MaxSize) + { + Size = MaxSize; + } + + byte[] Data = Context.Memory.ReadBytes(Position, Size); + + Buffer.BlockCopy(Data, 0, Storage.Data, (int)WritePosition, (int)Size); + } + + return 0; + } + + public long Read(ServiceCtx Context) + { + //TODO: Error conditions. + long ReadPosition = Context.RequestData.ReadInt64(); + + (long Position, long Size) = Context.Request.GetBufferType0x22(); + + byte[] Data; + + if (Storage.Data.Length > Size) + { + Data = new byte[Size]; + + Buffer.BlockCopy(Storage.Data, 0, Data, 0, (int)Size); + } + else + { + Data = Storage.Data; + } + + Context.Memory.WriteBytes(Position, Data); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/ISystemAppletProxy.cs b/Ryujinx.HLE/OsHle/Services/Am/ISystemAppletProxy.cs new file mode 100644 index 00000000..e0d78e34 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/ISystemAppletProxy.cs @@ -0,0 +1,99 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class ISystemAppletProxy : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISystemAppletProxy() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetCommonStateGetter }, + { 1, GetSelfController }, + { 2, GetWindowController }, + { 3, GetAudioController }, + { 4, GetDisplayController }, + { 11, GetLibraryAppletCreator }, + { 20, GetHomeMenuFunctions }, + { 21, GetGlobalStateController }, + { 22, GetApplicationCreator }, + { 1000, GetDebugFunctions } + }; + } + + public long GetCommonStateGetter(ServiceCtx Context) + { + MakeObject(Context, new ICommonStateGetter()); + + return 0; + } + + public long GetSelfController(ServiceCtx Context) + { + MakeObject(Context, new ISelfController()); + + return 0; + } + + public long GetWindowController(ServiceCtx Context) + { + MakeObject(Context, new IWindowController()); + + return 0; + } + + public long GetAudioController(ServiceCtx Context) + { + MakeObject(Context, new IAudioController()); + + return 0; + } + + public long GetDisplayController(ServiceCtx Context) + { + MakeObject(Context, new IDisplayController()); + + return 0; + } + + public long GetLibraryAppletCreator(ServiceCtx Context) + { + MakeObject(Context, new ILibraryAppletCreator()); + + return 0; + } + + public long GetHomeMenuFunctions(ServiceCtx Context) + { + MakeObject(Context, new IHomeMenuFunctions()); + + return 0; + } + + public long GetGlobalStateController(ServiceCtx Context) + { + MakeObject(Context, new IGlobalStateController()); + + return 0; + } + + public long GetApplicationCreator(ServiceCtx Context) + { + MakeObject(Context, new IApplicationCreator()); + + return 0; + } + + public long GetDebugFunctions(ServiceCtx Context) + { + MakeObject(Context, new IDebugFunctions()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/IWindowController.cs b/Ryujinx.HLE/OsHle/Services/Am/IWindowController.cs new file mode 100644 index 00000000..d9ab5db3 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/IWindowController.cs @@ -0,0 +1,38 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class IWindowController : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IWindowController() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 1, GetAppletResourceUserId }, + { 10, AcquireForegroundRights } + }; + } + + public long GetAppletResourceUserId(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + Context.ResponseData.Write(0L); + + return 0; + } + + public long AcquireForegroundRights(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAm, "Stubbed."); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/MessageInfo.cs b/Ryujinx.HLE/OsHle/Services/Am/MessageInfo.cs new file mode 100644 index 00000000..bae985fb --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/MessageInfo.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.OsHle.Services.Am +{ + enum MessageInfo + { + FocusStateChanged = 0xf, + OperationModeChanged = 0x1e, + PerformanceModeChanged = 0x1f + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/OperationMode.cs b/Ryujinx.HLE/OsHle/Services/Am/OperationMode.cs new file mode 100644 index 00000000..632ce931 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/OperationMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Am +{ + enum OperationMode + { + Handheld = 0, + Docked = 1 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Am/StorageHelper.cs b/Ryujinx.HLE/OsHle/Services/Am/StorageHelper.cs new file mode 100644 index 00000000..56e2a652 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Am/StorageHelper.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.HLE.OsHle.Services.Am +{ + class StorageHelper + { + private const uint LaunchParamsMagic = 0xc79497ca; + + public static byte[] MakeLaunchParams() + { + //Size needs to be at least 0x88 bytes otherwise application errors. + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + MS.SetLength(0x88); + + Writer.Write(LaunchParamsMagic); + Writer.Write(1); //IsAccountSelected? Only lower 8 bits actually used. + Writer.Write(1L); //User Id Low (note: User Id needs to be != 0) + Writer.Write(0L); //User Id High + + return MS.ToArray(); + } + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Apm/IManager.cs b/Ryujinx.HLE/OsHle/Services/Apm/IManager.cs new file mode 100644 index 00000000..22150d6e --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Apm/IManager.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Apm +{ + class IManager : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IManager() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, OpenSession } + }; + } + + public long OpenSession(ServiceCtx Context) + { + MakeObject(Context, new ISession()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Apm/ISession.cs b/Ryujinx.HLE/OsHle/Services/Apm/ISession.cs new file mode 100644 index 00000000..3c9bf07c --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Apm/ISession.cs @@ -0,0 +1,41 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Apm +{ + class ISession : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISession() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, SetPerformanceConfiguration }, + { 1, GetPerformanceConfiguration } + }; + } + + public long SetPerformanceConfiguration(ServiceCtx Context) + { + PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32(); + PerformanceConfiguration PerfConfig = (PerformanceConfiguration)Context.RequestData.ReadInt32(); + + return 0; + } + + public long GetPerformanceConfiguration(ServiceCtx Context) + { + PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32(); + + Context.ResponseData.Write((uint)PerformanceConfiguration.PerformanceConfiguration1); + + Context.Ns.Log.PrintStub(LogClass.ServiceApm, "Stubbed."); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Apm/PerformanceConfiguration.cs b/Ryujinx.HLE/OsHle/Services/Apm/PerformanceConfiguration.cs new file mode 100644 index 00000000..07d59285 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Apm/PerformanceConfiguration.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.OsHle.Services.Apm +{ + enum PerformanceConfiguration : uint + { + PerformanceConfiguration1 = 0x00010000, + PerformanceConfiguration2 = 0x00010001, + PerformanceConfiguration3 = 0x00010002, + PerformanceConfiguration4 = 0x00020000, + PerformanceConfiguration5 = 0x00020001, + PerformanceConfiguration6 = 0x00020002, + PerformanceConfiguration7 = 0x00020003, + PerformanceConfiguration8 = 0x00020004, + PerformanceConfiguration9 = 0x00020005, + PerformanceConfiguration10 = 0x00020006, + PerformanceConfiguration11 = 0x92220007, + PerformanceConfiguration12 = 0x92220008 + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Apm/PerformanceMode.cs b/Ryujinx.HLE/OsHle/Services/Apm/PerformanceMode.cs new file mode 100644 index 00000000..d89e2760 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Apm/PerformanceMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Apm +{ + enum PerformanceMode + { + Handheld = 0, + Docked = 1 + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/AudioOutData.cs b/Ryujinx.HLE/OsHle/Services/Aud/AudioOutData.cs new file mode 100644 index 00000000..9d68c24a --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Aud/AudioOutData.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.OsHle.Services.Aud +{ + [StructLayout(LayoutKind.Sequential)] + struct AudioOutData + { + public long NextBufferPtr; + public long SampleBufferPtr; + public long SampleBufferCapacity; + public long SampleBufferSize; + public long SampleBufferInnerOffset; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioDevice.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioDevice.cs new file mode 100644 index 00000000..67c0d837 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Aud/IAudioDevice.cs @@ -0,0 +1,222 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.HLE.OsHle.Services.Aud +{ + class IAudioDevice : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private KEvent SystemEvent; + + public IAudioDevice() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, ListAudioDeviceName }, + { 1, SetAudioDeviceOutputVolume }, + { 3, GetActiveAudioDeviceName }, + { 4, QueryAudioDeviceSystemEvent }, + { 5, GetActiveChannelCount }, + { 6, ListAudioDeviceNameAuto }, + { 7, SetAudioDeviceOutputVolumeAuto }, + { 8, GetAudioDeviceOutputVolumeAuto }, + { 10, GetActiveAudioDeviceNameAuto }, + { 11, QueryAudioDeviceInputEvent }, + { 12, QueryAudioDeviceOutputEvent } + }; + + SystemEvent = new KEvent(); + + //TODO: We shouldn't be signaling this here. + SystemEvent.WaitEvent.Set(); + } + + public long ListAudioDeviceName(ServiceCtx Context) + { + string[] DeviceNames = SystemStateMgr.AudioOutputs; + + Context.ResponseData.Write(DeviceNames.Length); + + long Position = Context.Request.ReceiveBuff[0].Position; + long Size = Context.Request.ReceiveBuff[0].Size; + + long BasePosition = Position; + + foreach (string Name in DeviceNames) + { + byte[] Buffer = Encoding.ASCII.GetBytes(Name + "\0"); + + if ((Position - BasePosition) + Buffer.Length > Size) + { + Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + + break; + } + + Context.Memory.WriteBytes(Position, Buffer); + + Position += Buffer.Length; + } + + return 0; + } + + public long SetAudioDeviceOutputVolume(ServiceCtx Context) + { + float Volume = Context.RequestData.ReadSingle(); + + long Position = Context.Request.SendBuff[0].Position; + long Size = Context.Request.SendBuff[0].Size; + + byte[] DeviceNameBuffer = Context.Memory.ReadBytes(Position, Size); + + string DeviceName = Encoding.ASCII.GetString(DeviceNameBuffer); + + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long GetActiveAudioDeviceName(ServiceCtx Context) + { + string Name = Context.Ns.Os.SystemState.ActiveAudioOutput; + + long Position = Context.Request.ReceiveBuff[0].Position; + long Size = Context.Request.ReceiveBuff[0].Size; + + byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(Name + "\0"); + + if ((ulong)DeviceNameBuffer.Length <= (ulong)Size) + { + Context.Memory.WriteBytes(Position, DeviceNameBuffer); + } + else + { + Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + } + + return 0; + } + + public long QueryAudioDeviceSystemEvent(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long GetActiveChannelCount(ServiceCtx Context) + { + Context.ResponseData.Write(2); + + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long ListAudioDeviceNameAuto(ServiceCtx Context) + { + string[] DeviceNames = SystemStateMgr.AudioOutputs; + + Context.ResponseData.Write(DeviceNames.Length); + + (long Position, long Size) = Context.Request.GetBufferType0x22(); + + long BasePosition = Position; + + foreach (string Name in DeviceNames) + { + byte[] Buffer = Encoding.UTF8.GetBytes(Name + '\0'); + + if ((Position - BasePosition) + Buffer.Length > Size) + { + Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + + break; + } + + Context.Memory.WriteBytes(Position, Buffer); + + Position += Buffer.Length; + } + + return 0; + } + + public long SetAudioDeviceOutputVolumeAuto(ServiceCtx Context) + { + float Volume = Context.RequestData.ReadSingle(); + + (long Position, long Size) = Context.Request.GetBufferType0x21(); + + byte[] DeviceNameBuffer = Context.Memory.ReadBytes(Position, Size); + + string DeviceName = Encoding.UTF8.GetString(DeviceNameBuffer); + + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long GetAudioDeviceOutputVolumeAuto(ServiceCtx Context) + { + Context.ResponseData.Write(1f); + + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long GetActiveAudioDeviceNameAuto(ServiceCtx Context) + { + string Name = Context.Ns.Os.SystemState.ActiveAudioOutput; + + (long Position, long Size) = Context.Request.GetBufferType0x22(); + + byte[] DeviceNameBuffer = Encoding.UTF8.GetBytes(Name + '\0'); + + if ((ulong)DeviceNameBuffer.Length <= (ulong)Size) + { + Context.Memory.WriteBytes(Position, DeviceNameBuffer); + } + else + { + Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + } + + return 0; + } + + public long QueryAudioDeviceInputEvent(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long QueryAudioDeviceOutputEvent(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioOut.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioOut.cs new file mode 100644 index 00000000..ef8bd89b --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Aud/IAudioOut.cs @@ -0,0 +1,154 @@ +using ChocolArm64.Memory; +using Ryujinx.Audio; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Aud +{ + class IAudioOut : IpcService, IDisposable + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private IAalOutput AudioOut; + + private KEvent ReleaseEvent; + + private int Track; + + public IAudioOut(IAalOutput AudioOut, KEvent ReleaseEvent, int Track) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetAudioOutState }, + { 1, StartAudioOut }, + { 2, StopAudioOut }, + { 3, AppendAudioOutBuffer }, + { 4, RegisterBufferEvent }, + { 5, GetReleasedAudioOutBuffer }, + { 6, ContainsAudioOutBuffer }, + { 7, AppendAudioOutBufferEx }, + { 8, GetReleasedAudioOutBufferEx } + }; + + this.AudioOut = AudioOut; + this.ReleaseEvent = ReleaseEvent; + this.Track = Track; + } + + public long GetAudioOutState(ServiceCtx Context) + { + Context.ResponseData.Write((int)AudioOut.GetState(Track)); + + return 0; + } + + public long StartAudioOut(ServiceCtx Context) + { + AudioOut.Start(Track); + + return 0; + } + + public long StopAudioOut(ServiceCtx Context) + { + AudioOut.Stop(Track); + + return 0; + } + + public long AppendAudioOutBuffer(ServiceCtx Context) + { + long Tag = Context.RequestData.ReadInt64(); + + AudioOutData Data = AMemoryHelper.Read<AudioOutData>( + Context.Memory, + Context.Request.SendBuff[0].Position); + + byte[] Buffer = Context.Memory.ReadBytes( + Data.SampleBufferPtr, + Data.SampleBufferSize); + + AudioOut.AppendBuffer(Track, Tag, Buffer); + + return 0; + } + + public long RegisterBufferEvent(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + + public long GetReleasedAudioOutBuffer(ServiceCtx Context) + { + long Position = Context.Request.ReceiveBuff[0].Position; + long Size = Context.Request.ReceiveBuff[0].Size; + + uint Count = (uint)((ulong)Size >> 3); + + long[] ReleasedBuffers = AudioOut.GetReleasedBuffers(Track, (int)Count); + + for (uint Index = 0; Index < Count; Index++) + { + long Tag = 0; + + if (Index < ReleasedBuffers.Length) + { + Tag = ReleasedBuffers[Index]; + } + + Context.Memory.WriteInt64(Position + Index * 8, Tag); + } + + Context.ResponseData.Write(ReleasedBuffers.Length); + + return 0; + } + + public long ContainsAudioOutBuffer(ServiceCtx Context) + { + long Tag = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(AudioOut.ContainsBuffer(Track, Tag) ? 1 : 0); + + return 0; + } + + public long AppendAudioOutBufferEx(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long GetReleasedAudioOutBufferEx(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + AudioOut.CloseTrack(Track); + + ReleaseEvent.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioOutManager.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioOutManager.cs new file mode 100644 index 00000000..a6b30835 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Aud/IAudioOutManager.cs @@ -0,0 +1,115 @@ +using ChocolArm64.Memory; +using Ryujinx.Audio; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.HLE.OsHle.Services.Aud +{ + class IAudioOutManager : IpcService + { + private const string DefaultAudioOutput = "DeviceOut"; + + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IAudioOutManager() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, ListAudioOuts }, + { 1, OpenAudioOut } + }; + } + + public long ListAudioOuts(ServiceCtx Context) + { + long Position = Context.Request.ReceiveBuff[0].Position; + long Size = Context.Request.ReceiveBuff[0].Size; + + int NameCount = 0; + + byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioOutput + "\0"); + + if ((ulong)DeviceNameBuffer.Length <= (ulong)Size) + { + Context.Memory.WriteBytes(Position, DeviceNameBuffer); + + NameCount++; + } + else + { + Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + } + + Context.ResponseData.Write(NameCount); + + return 0; + } + + public long OpenAudioOut(ServiceCtx Context) + { + IAalOutput AudioOut = Context.Ns.AudioOut; + + string DeviceName = AMemoryHelper.ReadAsciiString( + Context.Memory, + Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + if (DeviceName == string.Empty) + { + DeviceName = DefaultAudioOutput; + } + + long Position = Context.Request.ReceiveBuff[0].Position; + long Size = Context.Request.ReceiveBuff[0].Size; + + byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DeviceName + "\0"); + + if ((ulong)DeviceNameBuffer.Length <= (ulong)Size) + { + Context.Memory.WriteBytes(Position, DeviceNameBuffer); + } + else + { + Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!"); + } + + int SampleRate = Context.RequestData.ReadInt32(); + int Channels = Context.RequestData.ReadInt32(); + + Channels = (ushort)(Channels >> 16); + + if (SampleRate == 0) + { + SampleRate = 48000; + } + + if (Channels < 1 || Channels > 2) + { + Channels = 2; + } + + KEvent ReleaseEvent = new KEvent(); + + ReleaseCallback Callback = () => + { + ReleaseEvent.WaitEvent.Set(); + }; + + int Track = AudioOut.OpenTrack(SampleRate, Channels, Callback, out AudioFormat Format); + + MakeObject(Context, new IAudioOut(AudioOut, ReleaseEvent, Track)); + + Context.ResponseData.Write(SampleRate); + Context.ResponseData.Write(Channels); + Context.ResponseData.Write((int)Format); + Context.ResponseData.Write((int)PlaybackState.Stopped); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioRenderer.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioRenderer.cs new file mode 100644 index 00000000..9c495db5 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Aud/IAudioRenderer.cs @@ -0,0 +1,92 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Aud +{ + class IAudioRenderer : IpcService, IDisposable + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private KEvent UpdateEvent; + + public IAudioRenderer() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 4, RequestUpdateAudioRenderer }, + { 5, StartAudioRenderer }, + { 6, StopAudioRenderer }, + { 7, QuerySystemEvent } + }; + + UpdateEvent = new KEvent(); + } + + public long RequestUpdateAudioRenderer(ServiceCtx Context) + { + //(buffer<unknown, 5, 0>) -> (buffer<unknown, 6, 0>, buffer<unknown, 6, 0>) + + long Position = Context.Request.ReceiveBuff[0].Position; + + //0x40 bytes header + Context.Memory.WriteInt32(Position + 0x4, 0xb0); //Behavior Out State Size? (note: this is the last section) + Context.Memory.WriteInt32(Position + 0x8, 0x18e0); //Memory Pool Out State Size? + Context.Memory.WriteInt32(Position + 0xc, 0x600); //Voice Out State Size? + Context.Memory.WriteInt32(Position + 0x14, 0xe0); //Effect Out State Size? + Context.Memory.WriteInt32(Position + 0x1c, 0x20); //Sink Out State Size? + Context.Memory.WriteInt32(Position + 0x20, 0x10); //Performance Out State Size? + Context.Memory.WriteInt32(Position + 0x3c, 0x20e0); //Total Size (including 0x40 bytes header) + + for (int Offset = 0x40; Offset < 0x40 + 0x18e0; Offset += 0x10) + { + Context.Memory.WriteInt32(Position + Offset, 5); + } + + //TODO: We shouldn't be signaling this here. + UpdateEvent.WaitEvent.Set(); + + return 0; + } + + public long StartAudioRenderer(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long StopAudioRenderer(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceAudio, "Stubbed."); + + return 0; + } + + public long QuerySystemEvent(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(UpdateEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + UpdateEvent.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Aud/IAudioRendererManager.cs b/Ryujinx.HLE/OsHle/Services/Aud/IAudioRendererManager.cs new file mode 100644 index 00000000..6c0ba870 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Aud/IAudioRendererManager.cs @@ -0,0 +1,135 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Aud +{ + class IAudioRendererManager : IpcService + { + private const int Rev0Magic = ('R' << 0) | + ('E' << 8) | + ('V' << 16) | + ('0' << 24); + + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IAudioRendererManager() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, OpenAudioRenderer }, + { 1, GetAudioRendererWorkBufferSize }, + { 2, GetAudioDevice } + }; + } + + public long OpenAudioRenderer(ServiceCtx Context) + { + //Same buffer as GetAudioRendererWorkBufferSize is receive here. + + MakeObject(Context, new IAudioRenderer()); + + return 0; + } + + public long GetAudioRendererWorkBufferSize(ServiceCtx Context) + { + long SampleRate = Context.RequestData.ReadUInt32(); + long Unknown4 = Context.RequestData.ReadUInt32(); + long Unknown8 = Context.RequestData.ReadUInt32(); + long UnknownC = Context.RequestData.ReadUInt32(); + long Unknown10 = Context.RequestData.ReadUInt32(); //VoiceCount + long Unknown14 = Context.RequestData.ReadUInt32(); //SinkCount + long Unknown18 = Context.RequestData.ReadUInt32(); //EffectCount + long Unknown1c = Context.RequestData.ReadUInt32(); //Boolean + long Unknown20 = Context.RequestData.ReadUInt32(); //Not used here in FW3.0.1 - Boolean + long Unknown24 = Context.RequestData.ReadUInt32(); + long Unknown28 = Context.RequestData.ReadUInt32(); //SplitterCount + long Unknown2c = Context.RequestData.ReadUInt32(); //Not used here in FW3.0.1 + int RevMagic = Context.RequestData.ReadInt32(); + + int Version = (RevMagic - Rev0Magic) >> 24; + + if (Version <= 3) //REV3 Max is supported + { + long Size = RoundUp(Unknown8 * 4, 64); + Size += (UnknownC << 10); + Size += (UnknownC + 1) * 0x940; + Size += Unknown10 * 0x3F0; + Size += RoundUp((UnknownC + 1) * 8, 16); + Size += RoundUp(Unknown10 * 8, 16); + Size += RoundUp((0x3C0 * (Unknown14 + UnknownC) + 4 * Unknown4) * (Unknown8 + 6), 64); + Size += 0x2C0 * (Unknown14 + UnknownC) + 0x30 * (Unknown18 + (4 * Unknown10)) + 0x50; + + if (Version >= 3) //IsSplitterSupported + { + Size += RoundUp((NodeStatesGetWorkBufferSize((int)UnknownC + 1) + EdgeMatrixGetWorkBufferSize((int)UnknownC + 1)), 16); + Size += 0xE0 * Unknown28 + 0x20 * Unknown24 + RoundUp(Unknown28 * 4, 16); + } + + Size = 0x4C0 * Unknown18 + RoundUp(Size, 64) + 0x170 * Unknown14 + ((Unknown10 << 8) | 0x40); + + if (Unknown1c >= 1) + { + Size += ((((Unknown18 + Unknown14 + Unknown10 + UnknownC + 1) * 16) + 0x658) * (Unknown1c + 1) + 0x13F) & ~0x3FL; + } + + long WorkBufferSize = (Size + 0x1907D) & ~0xFFFL; + + Context.ResponseData.Write(WorkBufferSize); + + Context.Ns.Log.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{WorkBufferSize:x16}."); + + return 0; + } + else + { + Context.ResponseData.Write(0L); + + Context.Ns.Log.PrintError(LogClass.ServiceAudio, $"Library Revision 0x{RevMagic:x8} is not supported!"); + + return 0x499; + } + } + + private static long RoundUp(long Value, int Size) + { + return (Value + (Size - 1)) & ~((long)Size - 1); + } + + private static int NodeStatesGetWorkBufferSize(int Value) + { + int Result = (int)RoundUp(Value, 64); + + if (Result < 0) + { + Result |= 7; + } + + return 4 * (Value * Value) + 0x12 * Value + 2 * (Result / 8); + } + + private static int EdgeMatrixGetWorkBufferSize(int Value) + { + int Result = (int)RoundUp(Value * Value, 64); + + if (Result < 0) + { + Result |= 7; + } + + return Result / 8; + } + + public long GetAudioDevice(ServiceCtx Context) + { + long UserId = Context.RequestData.ReadInt64(); + + MakeObject(Context, new IAudioDevice()); + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Bsd/BsdError.cs b/Ryujinx.HLE/OsHle/Services/Bsd/BsdError.cs new file mode 100644 index 00000000..114130dc --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Bsd/BsdError.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Bsd +{ + //bsd_errno == (SocketException.ErrorCode - 10000) + public enum BsdError + { + Timeout = 60 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Bsd/BsdSocket.cs b/Ryujinx.HLE/OsHle/Services/Bsd/BsdSocket.cs new file mode 100644 index 00000000..9cde9947 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Bsd/BsdSocket.cs @@ -0,0 +1,18 @@ +using System.Net; +using System.Net.Sockets; + +namespace Ryujinx.HLE.OsHle.Services.Bsd +{ + class BsdSocket + { + public int Family; + public int Type; + public int Protocol; + + public IPAddress IpAddress; + + public IPEndPoint RemoteEP; + + public Socket Handle; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Bsd/IClient.cs b/Ryujinx.HLE/OsHle/Services/Bsd/IClient.cs new file mode 100644 index 00000000..15ce92a1 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Bsd/IClient.cs @@ -0,0 +1,445 @@ +using Ryujinx.HLE.OsHle.Ipc; +using Ryujinx.HLE.OsHle.Utilities; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.OsHle.Services.Bsd +{ + class IClient : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private List<BsdSocket> Sockets = new List<BsdSocket>(); + + public IClient() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Initialize }, + { 1, StartMonitoring }, + { 2, Socket }, + { 6, Poll }, + { 8, Recv }, + { 10, Send }, + { 11, SendTo }, + { 12, Accept }, + { 13, Bind }, + { 14, Connect }, + { 18, Listen }, + { 21, SetSockOpt }, + { 26, Close } + }; + } + + //(u32, u32, u32, u32, u32, u32, u32, u32, u64 pid, u64 transferMemorySize, pid, KObject) -> u32 bsd_errno + public long Initialize(ServiceCtx Context) + { + /* + typedef struct { + u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. + u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed). + u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed). + u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes). + u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes). + u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8). + } BsdBufferConfig; + */ + + Context.ResponseData.Write(0); + + //Todo: Stub + + return 0; + } + + //(u64, pid) + public long StartMonitoring(ServiceCtx Context) + { + //Todo: Stub + + return 0; + } + + //(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public long Socket(ServiceCtx Context) + { + BsdSocket NewBsdSocket = new BsdSocket + { + Family = Context.RequestData.ReadInt32(), + Type = Context.RequestData.ReadInt32(), + Protocol = Context.RequestData.ReadInt32() + }; + + Sockets.Add(NewBsdSocket); + + NewBsdSocket.Handle = new Socket((AddressFamily)NewBsdSocket.Family, + (SocketType)NewBsdSocket.Type, + (ProtocolType)NewBsdSocket.Protocol); + + Context.ResponseData.Write(Sockets.Count - 1); + Context.ResponseData.Write(0); + + return 0; + } + + //(u32, u32, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>) + public long Poll(ServiceCtx Context) + { + int PollCount = Context.RequestData.ReadInt32(); + int TimeOut = Context.RequestData.ReadInt32(); + + //https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/poll.h + //https://msdn.microsoft.com/fr-fr/library/system.net.sockets.socket.poll(v=vs.110).aspx + //https://github.com/switchbrew/libnx/blob/e0457c4534b3c37426d83e1a620f82cb28c3b528/nx/source/services/bsd.c#L343 + //https://github.com/TuxSH/ftpd/blob/switch_pr/source/ftp.c#L1634 + //https://linux.die.net/man/2/poll + + byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + int SocketId = Get32(SentBuffer, 0); + int RequestedEvents = Get16(SentBuffer, 4); + int ReturnedEvents = Get16(SentBuffer, 6); + + //Todo: Stub - Need to implemented the Type-22 buffer. + + Context.ResponseData.Write(1); + Context.ResponseData.Write(0); + + return 0; + } + + //(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, buffer<i8, 0x22, 0> message) + public long Recv(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + + byte[] ReceivedBuffer = new byte[Context.Request.ReceiveBuff[0].Size]; + + try + { + int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer); + + //Logging.Debug("Received Buffer:" + Environment.NewLine + Logging.HexDump(ReceivedBuffer)); + + Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, ReceivedBuffer); + + Context.ResponseData.Write(BytesRead); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, u32 flags, buffer<i8, 0x21, 0>) -> (i32 ret, u32 bsd_errno) + public long Send(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + + byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + try + { + //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); + + int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); + + Context.ResponseData.Write(BytesSent); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, u32 flags, buffer<i8, 0x21, 0>, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno) + public long SendTo(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + + byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[1].Position, + Context.Request.SendBuff[1].Size); + + if (!Sockets[SocketId].Handle.Connected) + { + try + { + ParseAddrBuffer(SocketId, AddressBuffer); + + Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + } + + try + { + //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer)); + + int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); + + Context.ResponseData.Write(BytesSent); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<sockaddr, 0x22, 0> addr) + public long Accept(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + long AddrBufferPtr = Context.Request.ReceiveBuff[0].Position; + + Socket HandleAccept = null; + + Task TimeOut = Task.Factory.StartNew(() => + { + try + { + HandleAccept = Sockets[SocketId].Handle.Accept(); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + }); + + TimeOut.Wait(10000); + + if (HandleAccept != null) + { + BsdSocket NewBsdSocket = new BsdSocket + { + IpAddress = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint).Address, + RemoteEP = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint), + Handle = HandleAccept + }; + + Sockets.Add(NewBsdSocket); + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write((byte)0); + + Writer.Write((byte)NewBsdSocket.Handle.AddressFamily); + + Writer.Write((short)((IPEndPoint)NewBsdSocket.Handle.LocalEndPoint).Port); + + byte[] IpAddress = NewBsdSocket.IpAddress.GetAddressBytes(); + + Writer.Write(IpAddress); + + Context.Memory.WriteBytes(AddrBufferPtr, MS.ToArray()); + + Context.ResponseData.Write(Sockets.Count - 1); + Context.ResponseData.Write(0); + Context.ResponseData.Write(MS.Length); + } + } + else + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write((int)BsdError.Timeout); + } + + return 0; + } + + //(u32 socket, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno) + public long Bind(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + try + { + ParseAddrBuffer(SocketId, AddressBuffer); + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno) + public long Connect(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position, + Context.Request.SendBuff[0].Size); + + try + { + ParseAddrBuffer(SocketId, AddressBuffer); + + Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP); + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) + public long Listen(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + int BackLog = Context.RequestData.ReadInt32(); + + try + { + Sockets[SocketId].Handle.Bind(Sockets[SocketId].RemoteEP); + Sockets[SocketId].Handle.Listen(BackLog); + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, u32 level, u32 option_name, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno) + public long SetSockOpt(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + SocketOptionLevel SocketLevel = (SocketOptionLevel)Context.RequestData.ReadInt32(); + SocketOptionName SocketOptionName = (SocketOptionName)Context.RequestData.ReadInt32(); + + byte[] SocketOptionValue = Context.Memory.ReadBytes(Context.Request.PtrBuff[0].Position, + Context.Request.PtrBuff[0].Size); + + int OptionValue = Get32(SocketOptionValue, 0); + + try + { + Sockets[SocketId].Handle.SetSocketOption(SocketLevel, SocketOptionName, OptionValue); + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket) -> (i32 ret, u32 bsd_errno) + public long Close(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + try + { + Sockets[SocketId].Handle.Close(); + Sockets[SocketId] = null; + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + public void ParseAddrBuffer(int SocketId, byte[] AddrBuffer) + { + using (MemoryStream MS = new MemoryStream(AddrBuffer)) + { + BinaryReader Reader = new BinaryReader(MS); + + int Size = Reader.ReadByte(); + int Family = Reader.ReadByte(); + int Port = EndianSwap.Swap16(Reader.ReadInt16()); + + string IpAddress = Reader.ReadByte().ToString() + "." + + Reader.ReadByte().ToString() + "." + + Reader.ReadByte().ToString() + "." + + Reader.ReadByte().ToString(); + + Sockets[SocketId].IpAddress = IPAddress.Parse(IpAddress); + + Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port); + } + } + + private int Get16(byte[] Data, int Address) + { + return + Data[Address + 0] << 0 | + Data[Address + 1] << 8; + } + + private int Get32(byte[] Data, int Address) + { + return + Data[Address + 0] << 0 | + Data[Address + 1] << 8 | + Data[Address + 2] << 16 | + Data[Address + 3] << 24; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Caps/IAlbumAccessorService.cs b/Ryujinx.HLE/OsHle/Services/Caps/IAlbumAccessorService.cs new file mode 100644 index 00000000..04a81f90 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Caps/IAlbumAccessorService.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Caps +{ + class IAlbumAccessorService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IAlbumAccessorService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Caps/IScreenshotService.cs b/Ryujinx.HLE/OsHle/Services/Caps/IScreenshotService.cs new file mode 100644 index 00000000..9b1005ed --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Caps/IScreenshotService.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Caps +{ + class IScreenshotService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IScreenshotService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Friend/IFriendService.cs b/Ryujinx.HLE/OsHle/Services/Friend/IFriendService.cs new file mode 100644 index 00000000..d5843ffb --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Friend/IFriendService.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Friend +{ + class IFriendService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IFriendService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Friend/IServiceCreator.cs b/Ryujinx.HLE/OsHle/Services/Friend/IServiceCreator.cs new file mode 100644 index 00000000..6b9a265f --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Friend/IServiceCreator.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Friend +{ + class IServiceCreator : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IServiceCreator() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, CreateFriendService } + }; + } + + public static long CreateFriendService(ServiceCtx Context) + { + MakeObject(Context, new IFriendService()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/FsErr.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/FsErr.cs new file mode 100644 index 00000000..bdc70959 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/FspSrv/FsErr.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.OsHle.Services.FspSrv +{ + static class FsErr + { + public const int PathDoesNotExist = 1; + public const int PathAlreadyExists = 2; + public const int PathAlreadyInUse = 7; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IDirectory.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IDirectory.cs new file mode 100644 index 00000000..bb4b7a03 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/FspSrv/IDirectory.cs @@ -0,0 +1,116 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.OsHle.Services.FspSrv +{ + class IDirectory : IpcService, IDisposable + { + private const int DirectoryEntrySize = 0x310; + + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private List<string> DirectoryEntries; + + private int CurrentItemIndex; + + public event EventHandler<EventArgs> Disposed; + + public string HostPath { get; private set; } + + public IDirectory(string HostPath, int Flags) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Read }, + { 1, GetEntryCount } + }; + + this.HostPath = HostPath; + + DirectoryEntries = new List<string>(); + + if ((Flags & 1) != 0) + { + DirectoryEntries.AddRange(Directory.GetDirectories(HostPath)); + } + + if ((Flags & 2) != 0) + { + DirectoryEntries.AddRange(Directory.GetFiles(HostPath)); + } + + CurrentItemIndex = 0; + } + + public long Read(ServiceCtx Context) + { + long BufferPosition = Context.Request.ReceiveBuff[0].Position; + long BufferLen = Context.Request.ReceiveBuff[0].Size; + + int MaxReadCount = (int)(BufferLen / DirectoryEntrySize); + + int Count = Math.Min(DirectoryEntries.Count - CurrentItemIndex, MaxReadCount); + + for (int Index = 0; Index < Count; Index++) + { + long Position = BufferPosition + Index * DirectoryEntrySize; + + WriteDirectoryEntry(Context, Position, DirectoryEntries[CurrentItemIndex++]); + } + + Context.ResponseData.Write((long)Count); + + return 0; + } + + private void WriteDirectoryEntry(ServiceCtx Context, long Position, string FullPath) + { + for (int Offset = 0; Offset < 0x300; Offset += 8) + { + Context.Memory.WriteInt64(Position + Offset, 0); + } + + byte[] NameBuffer = Encoding.UTF8.GetBytes(Path.GetFileName(FullPath)); + + Context.Memory.WriteBytes(Position, NameBuffer); + + int Type = 0; + long Size = 0; + + if (File.Exists(FullPath)) + { + Type = 1; + Size = new FileInfo(FullPath).Length; + } + + Context.Memory.WriteInt32(Position + 0x300, 0); //Padding? + Context.Memory.WriteInt32(Position + 0x304, Type); + Context.Memory.WriteInt64(Position + 0x308, Size); + } + + public long GetEntryCount(ServiceCtx Context) + { + Context.ResponseData.Write((long)DirectoryEntries.Count); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Disposed?.Invoke(this, EventArgs.Empty); + } + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IFile.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IFile.cs new file mode 100644 index 00000000..a610a3ab --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/FspSrv/IFile.cs @@ -0,0 +1,110 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.OsHle.Services.FspSrv +{ + class IFile : IpcService, IDisposable + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private Stream BaseStream; + + public event EventHandler<EventArgs> Disposed; + + public string HostPath { get; private set; } + + public IFile(Stream BaseStream, string HostPath) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Read }, + { 1, Write }, + { 2, Flush }, + { 3, SetSize }, + { 4, GetSize } + }; + + this.BaseStream = BaseStream; + this.HostPath = HostPath; + } + + public long Read(ServiceCtx Context) + { + long Position = Context.Request.ReceiveBuff[0].Position; + + long Zero = Context.RequestData.ReadInt64(); + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + byte[] Data = new byte[Size]; + + BaseStream.Seek(Offset, SeekOrigin.Begin); + + int ReadSize = BaseStream.Read(Data, 0, (int)Size); + + Context.Memory.WriteBytes(Position, Data); + + Context.ResponseData.Write((long)ReadSize); + + return 0; + } + + public long Write(ServiceCtx Context) + { + long Position = Context.Request.SendBuff[0].Position; + + long Zero = Context.RequestData.ReadInt64(); + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + byte[] Data = Context.Memory.ReadBytes(Position, Size); + + BaseStream.Seek(Offset, SeekOrigin.Begin); + BaseStream.Write(Data, 0, (int)Size); + + return 0; + } + + public long Flush(ServiceCtx Context) + { + BaseStream.Flush(); + + return 0; + } + + public long SetSize(ServiceCtx Context) + { + long Size = Context.RequestData.ReadInt64(); + + BaseStream.SetLength(Size); + + return 0; + } + + public long GetSize(ServiceCtx Context) + { + Context.ResponseData.Write(BaseStream.Length); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && BaseStream != null) + { + BaseStream.Dispose(); + + Disposed?.Invoke(this, EventArgs.Empty); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs new file mode 100644 index 00000000..441b7e8a --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystem.cs @@ -0,0 +1,399 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using static Ryujinx.HLE.OsHle.ErrorCode; + +namespace Ryujinx.HLE.OsHle.Services.FspSrv +{ + class IFileSystem : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private HashSet<string> OpenPaths; + + private string Path; + + public IFileSystem(string Path) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, CreateFile }, + { 1, DeleteFile }, + { 2, CreateDirectory }, + { 3, DeleteDirectory }, + { 4, DeleteDirectoryRecursively }, + { 5, RenameFile }, + { 6, RenameDirectory }, + { 7, GetEntryType }, + { 8, OpenFile }, + { 9, OpenDirectory }, + { 10, Commit }, + { 11, GetFreeSpaceSize }, + { 12, GetTotalSpaceSize }, + //{ 13, CleanDirectoryRecursively }, + //{ 14, GetFileTimeStampRaw } + }; + + OpenPaths = new HashSet<string>(); + + this.Path = Path; + } + + public long CreateFile(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + string Name = ReadUtf8String(Context); + + long Mode = Context.RequestData.ReadInt64(); + int Size = Context.RequestData.ReadInt32(); + + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (FileName == null) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (File.Exists(FileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); + } + + if (IsPathAlreadyInUse(FileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + using (FileStream NewFile = File.Create(FileName)) + { + NewFile.SetLength(Size); + } + + return 0; + } + + public long DeleteFile(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + string Name = ReadUtf8String(Context); + + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (!File.Exists(FileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (IsPathAlreadyInUse(FileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + File.Delete(FileName); + + return 0; + } + + public long CreateDirectory(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + string Name = ReadUtf8String(Context); + + string DirName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (DirName == null) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (Directory.Exists(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); + } + + if (IsPathAlreadyInUse(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + Directory.CreateDirectory(DirName); + + return 0; + } + + public long DeleteDirectory(ServiceCtx Context) + { + return DeleteDirectory(Context, false); + } + + public long DeleteDirectoryRecursively(ServiceCtx Context) + { + return DeleteDirectory(Context, true); + } + + private long DeleteDirectory(ServiceCtx Context, bool Recursive) + { + long Position = Context.Request.PtrBuff[0].Position; + + string Name = ReadUtf8String(Context); + + string DirName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (!Directory.Exists(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (IsPathAlreadyInUse(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + Directory.Delete(DirName, Recursive); + + return 0; + } + + public long RenameFile(ServiceCtx Context) + { + string OldName = ReadUtf8String(Context, 0); + string NewName = ReadUtf8String(Context, 1); + + string OldFileName = Context.Ns.VFs.GetFullPath(Path, OldName); + string NewFileName = Context.Ns.VFs.GetFullPath(Path, NewName); + + if (!File.Exists(OldFileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (File.Exists(NewFileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); + } + + if (IsPathAlreadyInUse(OldFileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + File.Move(OldFileName, NewFileName); + + return 0; + } + + public long RenameDirectory(ServiceCtx Context) + { + string OldName = ReadUtf8String(Context, 0); + string NewName = ReadUtf8String(Context, 1); + + string OldDirName = Context.Ns.VFs.GetFullPath(Path, OldName); + string NewDirName = Context.Ns.VFs.GetFullPath(Path, NewName); + + if (!Directory.Exists(OldDirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (Directory.Exists(NewDirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); + } + + if (IsPathAlreadyInUse(OldDirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + Directory.Move(OldDirName, NewDirName); + + return 0; + } + + public long GetEntryType(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + string Name = ReadUtf8String(Context); + + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (File.Exists(FileName)) + { + Context.ResponseData.Write(1); + } + else if (Directory.Exists(FileName)) + { + Context.ResponseData.Write(0); + } + else + { + Context.ResponseData.Write(0); + + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + return 0; + } + + public long OpenFile(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int FilterFlags = Context.RequestData.ReadInt32(); + + string Name = ReadUtf8String(Context); + + string FileName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (!File.Exists(FileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (IsPathAlreadyInUse(FileName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + FileStream Stream = new FileStream(FileName, FileMode.Open); + + IFile FileInterface = new IFile(Stream, FileName); + + FileInterface.Disposed += RemoveFileInUse; + + lock (OpenPaths) + { + OpenPaths.Add(FileName); + } + + MakeObject(Context, FileInterface); + + return 0; + } + + public long OpenDirectory(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int FilterFlags = Context.RequestData.ReadInt32(); + + string Name = ReadUtf8String(Context); + + string DirName = Context.Ns.VFs.GetFullPath(Path, Name); + + if (!Directory.Exists(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + if (IsPathAlreadyInUse(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse); + } + + IDirectory DirInterface = new IDirectory(DirName, FilterFlags); + + DirInterface.Disposed += RemoveDirectoryInUse; + + lock (OpenPaths) + { + OpenPaths.Add(DirName); + } + + MakeObject(Context, DirInterface); + + return 0; + } + + public long Commit(ServiceCtx Context) + { + return 0; + } + + public long GetFreeSpaceSize(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + string Name = ReadUtf8String(Context); + + Context.ResponseData.Write(Context.Ns.VFs.GetDrive().AvailableFreeSpace); + + return 0; + } + + public long GetTotalSpaceSize(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + string Name = ReadUtf8String(Context); + + Context.ResponseData.Write(Context.Ns.VFs.GetDrive().TotalSize); + + return 0; + } + + private bool IsPathAlreadyInUse(string Path) + { + lock (OpenPaths) + { + return OpenPaths.Contains(Path); + } + } + + private void RemoveFileInUse(object sender, EventArgs e) + { + IFile FileInterface = (IFile)sender; + + lock (OpenPaths) + { + FileInterface.Disposed -= RemoveFileInUse; + + OpenPaths.Remove(FileInterface.HostPath); + } + } + + private void RemoveDirectoryInUse(object sender, EventArgs e) + { + IDirectory DirInterface = (IDirectory)sender; + + lock (OpenPaths) + { + DirInterface.Disposed -= RemoveDirectoryInUse; + + OpenPaths.Remove(DirInterface.HostPath); + } + } + + private string ReadUtf8String(ServiceCtx Context, int Index = 0) + { + long Position = Context.Request.PtrBuff[Index].Position; + long Size = Context.Request.PtrBuff[Index].Size; + + using (MemoryStream MS = new MemoryStream()) + { + while (Size-- > 0) + { + byte Value = Context.Memory.ReadByte(Position++); + + if (Value == 0) + { + break; + } + + MS.WriteByte(Value); + } + + return Encoding.UTF8.GetString(MS.ToArray()); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystemProxy.cs new file mode 100644 index 00000000..84a0bc3d --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/FspSrv/IFileSystemProxy.cs @@ -0,0 +1,74 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.FspSrv +{ + class IFileSystemProxy : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IFileSystemProxy() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 1, SetCurrentProcess }, + { 18, OpenSdCardFileSystem }, + { 22, CreateSaveDataFileSystem }, + { 51, OpenSaveDataFileSystem }, + { 200, OpenDataStorageByCurrentProcess }, + { 203, OpenPatchDataStorageByCurrentProcess }, + { 1005, GetGlobalAccessLogMode } + }; + } + + public long SetCurrentProcess(ServiceCtx Context) + { + return 0; + } + + public long OpenSdCardFileSystem(ServiceCtx Context) + { + MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetSdCardPath())); + + return 0; + } + + public long CreateSaveDataFileSystem(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceFs, "Stubbed."); + + return 0; + } + + public long OpenSaveDataFileSystem(ServiceCtx Context) + { + MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetGameSavesPath())); + + return 0; + } + + public long OpenDataStorageByCurrentProcess(ServiceCtx Context) + { + MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs)); + + return 0; + } + + public long OpenPatchDataStorageByCurrentProcess(ServiceCtx Context) + { + MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs)); + + return 0; + } + + public long GetGlobalAccessLogMode(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/FspSrv/IStorage.cs b/Ryujinx.HLE/OsHle/Services/FspSrv/IStorage.cs new file mode 100644 index 00000000..56c27d03 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/FspSrv/IStorage.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.OsHle.Services.FspSrv +{ + class IStorage : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private Stream BaseStream; + + public IStorage(Stream BaseStream) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Read } + }; + + this.BaseStream = BaseStream; + } + + public long Read(ServiceCtx Context) + { + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + if (Context.Request.ReceiveBuff.Count > 0) + { + IpcBuffDesc BuffDesc = Context.Request.ReceiveBuff[0]; + + //Use smaller length to avoid overflows. + if (Size > BuffDesc.Size) + { + Size = BuffDesc.Size; + } + + byte[] Data = new byte[Size]; + + BaseStream.Seek(Offset, SeekOrigin.Begin); + BaseStream.Read(Data, 0, Data.Length); + + Context.Memory.WriteBytes(BuffDesc.Position, Data); + } + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Hid/IActiveVibrationDeviceList.cs b/Ryujinx.HLE/OsHle/Services/Hid/IActiveVibrationDeviceList.cs new file mode 100644 index 00000000..12eaf706 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Hid/IActiveVibrationDeviceList.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Hid +{ + class IActiveApplicationDeviceList : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IActiveApplicationDeviceList() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, ActivateVibrationDevice } + }; + } + + public long ActivateVibrationDevice(ServiceCtx Context) + { + int VibrationDeviceHandle = Context.RequestData.ReadInt32(); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Hid/IAppletResource.cs b/Ryujinx.HLE/OsHle/Services/Hid/IAppletResource.cs new file mode 100644 index 00000000..2ef67cc3 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Hid/IAppletResource.cs @@ -0,0 +1,34 @@ +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Hid +{ + class IAppletResource : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private HSharedMem HidSharedMem; + + public IAppletResource(HSharedMem HidSharedMem) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetSharedMemoryHandle } + }; + + this.HidSharedMem = HidSharedMem; + } + + public long GetSharedMemoryHandle(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(HidSharedMem); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Hid/IHidServer.cs b/Ryujinx.HLE/OsHle/Services/Hid/IHidServer.cs new file mode 100644 index 00000000..79d37fd4 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Hid/IHidServer.cs @@ -0,0 +1,270 @@ +using Ryujinx.HLE.Input; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Hid +{ + class IHidServer : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IHidServer() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, CreateAppletResource }, + { 1, ActivateDebugPad }, + { 11, ActivateTouchScreen }, + { 21, ActivateMouse }, + { 31, ActivateKeyboard }, + { 66, StartSixAxisSensor }, + { 79, SetGyroscopeZeroDriftMode }, + { 100, SetSupportedNpadStyleSet }, + { 101, GetSupportedNpadStyleSet }, + { 102, SetSupportedNpadIdType }, + { 103, ActivateNpad }, + { 108, GetPlayerLedPattern }, + { 120, SetNpadJoyHoldType }, + { 121, GetNpadJoyHoldType }, + { 122, SetNpadJoyAssignmentModeSingleByDefault }, + { 123, SetNpadJoyAssignmentModeSingle }, + { 124, SetNpadJoyAssignmentModeDual }, + { 125, MergeSingleJoyAsDualJoy }, + { 128, SetNpadHandheldActivationMode }, + { 200, GetVibrationDeviceInfo }, + { 201, SendVibrationValue }, + { 203, CreateActiveVibrationDeviceList }, + { 206, SendVibrationValues } + }; + } + + public long CreateAppletResource(ServiceCtx Context) + { + MakeObject(Context, new IAppletResource(Context.Ns.Os.HidSharedMem)); + + return 0; + } + + public long ActivateDebugPad(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long ActivateTouchScreen(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long ActivateMouse(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long ActivateKeyboard(ServiceCtx Context) + { + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long StartSixAxisSensor(ServiceCtx Context) + { + int Handle = Context.RequestData.ReadInt32(); + + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long SetGyroscopeZeroDriftMode(ServiceCtx Context) + { + int Handle = Context.RequestData.ReadInt32(); + int Unknown = Context.RequestData.ReadInt32(); + long AppletResourceUserId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long GetSupportedNpadStyleSet(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long SetSupportedNpadStyleSet(ServiceCtx Context) + { + long Unknown0 = Context.RequestData.ReadInt64(); + long Unknown8 = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long SetSupportedNpadIdType(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long ActivateNpad(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long GetPlayerLedPattern(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(0L); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long SetNpadJoyHoldType(ServiceCtx Context) + { + long Unknown0 = Context.RequestData.ReadInt64(); + long Unknown8 = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long GetNpadJoyHoldType(ServiceCtx Context) + { + Context.ResponseData.Write(0L); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx Context) + { + HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + + long AppletUserResourceId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long SetNpadJoyAssignmentModeSingle(ServiceCtx Context) + { + HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + + long AppletUserResourceId = Context.RequestData.ReadInt64(); + long NpadJoyDeviceType = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long SetNpadJoyAssignmentModeDual(ServiceCtx Context) + { + HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32(); + + long AppletUserResourceId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long MergeSingleJoyAsDualJoy(ServiceCtx Context) + { + long Unknown0 = Context.RequestData.ReadInt32(); + long Unknown8 = Context.RequestData.ReadInt32(); + long AppletUserResourceId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long SetNpadHandheldActivationMode(ServiceCtx Context) + { + long AppletUserResourceId = Context.RequestData.ReadInt64(); + long Unknown = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long GetVibrationDeviceInfo(ServiceCtx Context) + { + int VibrationDeviceHandle = Context.RequestData.ReadInt32(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + Context.ResponseData.Write(0L); //VibrationDeviceInfoForIpc + + return 0; + } + + public long SendVibrationValue(ServiceCtx Context) + { + int VibrationDeviceHandle = Context.RequestData.ReadInt32(); + + int VibrationValue1 = Context.RequestData.ReadInt32(); + int VibrationValue2 = Context.RequestData.ReadInt32(); + int VibrationValue3 = Context.RequestData.ReadInt32(); + int VibrationValue4 = Context.RequestData.ReadInt32(); + + long AppletUserResourceId = Context.RequestData.ReadInt64(); + + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + + public long CreateActiveVibrationDeviceList(ServiceCtx Context) + { + MakeObject(Context, new IActiveApplicationDeviceList()); + + return 0; + } + + public long SendVibrationValues(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceHid, "Stubbed."); + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/IIpcService.cs b/Ryujinx.HLE/OsHle/Services/IIpcService.cs new file mode 100644 index 00000000..60675380 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/IIpcService.cs @@ -0,0 +1,10 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services +{ + interface IIpcService + { + IReadOnlyDictionary<int, ServiceProcessRequest> Commands { get; } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/IpcService.cs b/Ryujinx.HLE/OsHle/Services/IpcService.cs new file mode 100644 index 00000000..25fd56fe --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/IpcService.cs @@ -0,0 +1,154 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.OsHle.Services +{ + abstract class IpcService : IIpcService + { + public abstract IReadOnlyDictionary<int, ServiceProcessRequest> Commands { get; } + + private IdDictionary DomainObjects; + + private int SelfId; + + private bool IsDomain; + + public IpcService() + { + DomainObjects = new IdDictionary(); + + SelfId = -1; + } + + public int ConvertToDomain() + { + if (SelfId == -1) + { + SelfId = DomainObjects.Add(this); + } + + IsDomain = true; + + return SelfId; + } + + public void ConvertToSession() + { + IsDomain = false; + } + + public void CallMethod(ServiceCtx Context) + { + IIpcService Service = this; + + if (IsDomain) + { + int DomainWord0 = Context.RequestData.ReadInt32(); + int DomainObjId = Context.RequestData.ReadInt32(); + + long Padding = Context.RequestData.ReadInt64(); + + int DomainCmd = DomainWord0 & 0xff; + + if (DomainCmd == 1) + { + Service = GetObject(DomainObjId); + + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + } + else if (DomainCmd == 2) + { + Delete(DomainObjId); + + Context.ResponseData.Write(0L); + + return; + } + else + { + throw new NotImplementedException($"Domain command: {DomainCmd}"); + } + } + + long SfciMagic = Context.RequestData.ReadInt64(); + int CommandId = (int)Context.RequestData.ReadInt64(); + + if (Service.Commands.TryGetValue(CommandId, out ServiceProcessRequest ProcessRequest)) + { + Context.ResponseData.BaseStream.Seek(IsDomain ? 0x20 : 0x10, SeekOrigin.Begin); + + Context.Ns.Log.PrintDebug(LogClass.KernelIpc, $"{Service.GetType().Name}: {ProcessRequest.Method.Name}"); + + long Result = ProcessRequest(Context); + + if (IsDomain) + { + foreach (int Id in Context.Response.ResponseObjIds) + { + Context.ResponseData.Write(Id); + } + + Context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); + + Context.ResponseData.Write(Context.Response.ResponseObjIds.Count); + } + + Context.ResponseData.BaseStream.Seek(IsDomain ? 0x10 : 0, SeekOrigin.Begin); + + Context.ResponseData.Write(IpcMagic.Sfco); + Context.ResponseData.Write(Result); + } + else + { + string DbgMessage = $"{Context.Session.ServiceName} {Service.GetType().Name}: {CommandId}"; + + throw new NotImplementedException(DbgMessage); + } + } + + protected static void MakeObject(ServiceCtx Context, IpcService Obj) + { + IpcService Service = Context.Session.Service; + + if (Service.IsDomain) + { + Context.Response.ResponseObjIds.Add(Service.Add(Obj)); + } + else + { + KSession Session = new KSession(Obj, Context.Session.ServiceName); + + int Handle = Context.Process.HandleTable.OpenHandle(Session); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + } + } + + private int Add(IIpcService Obj) + { + return DomainObjects.Add(Obj); + } + + private bool Delete(int Id) + { + object Obj = DomainObjects.Delete(Id); + + if (Obj is IDisposable DisposableObj) + { + DisposableObj.Dispose(); + } + + return Obj != null; + } + + private IIpcService GetObject(int Id) + { + return DomainObjects.GetData<IIpcService>(Id); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Lm/ILogService.cs b/Ryujinx.HLE/OsHle/Services/Lm/ILogService.cs new file mode 100644 index 00000000..c3aeb184 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Lm/ILogService.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Lm +{ + class ILogService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ILogService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Initialize } + }; + } + + public long Initialize(ServiceCtx Context) + { + MakeObject(Context, new ILogger()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Lm/ILogger.cs b/Ryujinx.HLE/OsHle/Services/Lm/ILogger.cs new file mode 100644 index 00000000..90edf2ad --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Lm/ILogger.cs @@ -0,0 +1,86 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.OsHle.Services.Lm +{ + class ILogger : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ILogger() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Log } + }; + } + + public long Log(ServiceCtx Context) + { + byte[] LogBuffer = Context.Memory.ReadBytes( + Context.Request.PtrBuff[0].Position, + Context.Request.PtrBuff[0].Size); + + using (MemoryStream MS = new MemoryStream(LogBuffer)) + { + BinaryReader Reader = new BinaryReader(MS); + + long Pid = Reader.ReadInt64(); + long ThreadContext = Reader.ReadInt64(); + short Flags = Reader.ReadInt16(); + byte Level = Reader.ReadByte(); + byte Verbosity = Reader.ReadByte(); + int PayloadLength = Reader.ReadInt32(); + + StringBuilder SB = new StringBuilder(); + + SB.AppendLine("Guest log:"); + + while (MS.Position < MS.Length) + { + byte Type = Reader.ReadByte(); + byte Size = Reader.ReadByte(); + + LmLogField Field = (LmLogField)Type; + + string FieldStr = string.Empty; + + if (Field == LmLogField.Skip) + { + Reader.ReadByte(); + + continue; + } + else if (Field == LmLogField.Line) + { + FieldStr = Field + ": " + Reader.ReadInt32(); + } + else + { + FieldStr = Field + ": \"" + Encoding.UTF8.GetString(Reader.ReadBytes(Size)) + "\""; + } + + SB.AppendLine(" " + FieldStr); + } + + string Text = SB.ToString(); + + switch((LmLogLevel)Level) + { + case LmLogLevel.Trace: Context.Ns.Log.PrintDebug (LogClass.ServiceLm, Text); break; + case LmLogLevel.Info: Context.Ns.Log.PrintInfo (LogClass.ServiceLm, Text); break; + case LmLogLevel.Warning: Context.Ns.Log.PrintWarning(LogClass.ServiceLm, Text); break; + case LmLogLevel.Error: Context.Ns.Log.PrintError (LogClass.ServiceLm, Text); break; + case LmLogLevel.Critical: Context.Ns.Log.PrintError (LogClass.ServiceLm, Text); break; + } + } + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Lm/LmLogField.cs b/Ryujinx.HLE/OsHle/Services/Lm/LmLogField.cs new file mode 100644 index 00000000..33593103 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Lm/LmLogField.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.OsHle.Services.Lm +{ + enum LmLogField + { + Skip = 1, + Message = 2, + Line = 3, + Filename = 4, + Function = 5, + Module = 6, + Thread = 7 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Lm/LmLogLevel.cs b/Ryujinx.HLE/OsHle/Services/Lm/LmLogLevel.cs new file mode 100644 index 00000000..d051a595 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Lm/LmLogLevel.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.OsHle.Services.Lm +{ + enum LmLogLevel + { + Trace, + Info, + Warning, + Error, + Critical + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Mm/IRequest.cs b/Ryujinx.HLE/OsHle/Services/Mm/IRequest.cs new file mode 100644 index 00000000..c60b7f52 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Mm/IRequest.cs @@ -0,0 +1,46 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Mm +{ + class IRequest : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IRequest() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 4, Initialize }, + { 6, SetAndWait }, + { 7, Get } + }; + } + + public long Initialize(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); + + return 0; + } + + public long SetAndWait(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); + + return 0; + } + + public long Get(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + Context.Ns.Log.PrintStub(LogClass.ServiceMm, "Stubbed."); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nfp/DeviceState.cs b/Ryujinx.HLE/OsHle/Services/Nfp/DeviceState.cs new file mode 100644 index 00000000..1863e0d9 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nfp/DeviceState.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.OsHle.Services.Nfp +{ + enum DeviceState + { + Initialized = 0 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nfp/IUser.cs b/Ryujinx.HLE/OsHle/Services/Nfp/IUser.cs new file mode 100644 index 00000000..4b423ba7 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nfp/IUser.cs @@ -0,0 +1,114 @@ +using Ryujinx.HLE.Input; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Nfp +{ + class IUser : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private const HidControllerId NpadId = HidControllerId.CONTROLLER_PLAYER_1; + + private State State = State.NonInitialized; + + private DeviceState DeviceState = DeviceState.Initialized; + + private KEvent ActivateEvent; + + private KEvent DeactivateEvent; + + private KEvent AvailabilityChangeEvent; + + public IUser() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Initialize }, + { 17, AttachActivateEvent }, + { 18, AttachDeactivateEvent }, + { 19, GetState }, + { 20, GetDeviceState }, + { 21, GetNpadId }, + { 23, AttachAvailabilityChangeEvent } + }; + + ActivateEvent = new KEvent(); + DeactivateEvent = new KEvent(); + AvailabilityChangeEvent = new KEvent(); + } + + public long Initialize(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + + State = State.Initialized; + + return 0; + } + + public long AttachActivateEvent(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + + int Handle = Context.Process.HandleTable.OpenHandle(ActivateEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);; + + return 0; + } + + public long AttachDeactivateEvent(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + + int Handle = Context.Process.HandleTable.OpenHandle(DeactivateEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + + public long GetState(ServiceCtx Context) + { + Context.ResponseData.Write((int)State); + + Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + + return 0; + } + + public long GetDeviceState(ServiceCtx Context) + { + Context.ResponseData.Write((int)DeviceState); + + Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + + return 0; + } + + public long GetNpadId(ServiceCtx Context) + { + Context.ResponseData.Write((int)NpadId); + + Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + + return 0; + } + + public long AttachAvailabilityChangeEvent(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNfp, "Stubbed."); + + int Handle = Context.Process.HandleTable.OpenHandle(AvailabilityChangeEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nfp/IUserManager.cs b/Ryujinx.HLE/OsHle/Services/Nfp/IUserManager.cs new file mode 100644 index 00000000..845ce7cf --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nfp/IUserManager.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Nfp +{ + class IUserManager : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IUserManager() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetUserInterface } + }; + } + + public long GetUserInterface(ServiceCtx Context) + { + MakeObject(Context, new IUser()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nfp/State.cs b/Ryujinx.HLE/OsHle/Services/Nfp/State.cs new file mode 100644 index 00000000..c1f0bb1a --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nfp/State.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Nfp +{ + enum State + { + NonInitialized = 0, + Initialized = 1 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs b/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs new file mode 100644 index 00000000..e289a8db --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nifm/IGeneralService.cs @@ -0,0 +1,33 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Nifm +{ + class IGeneralService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IGeneralService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 4, CreateRequest } + }; + } + + //CreateRequest(i32) + public long CreateRequest(ServiceCtx Context) + { + int Unknown = Context.RequestData.ReadInt32(); + + MakeObject(Context, new IRequest()); + + Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs b/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs new file mode 100644 index 00000000..c8c679c4 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nifm/IRequest.cs @@ -0,0 +1,93 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Nifm +{ + class IRequest : IpcService, IDisposable + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private KEvent Event; + + public IRequest() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetRequestState }, + { 1, GetResult }, + { 2, GetSystemEventReadableHandles }, + { 3, Cancel }, + { 4, Submit }, + { 11, SetConnectionConfirmationOption } + }; + + Event = new KEvent(); + } + + public long GetRequestState(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + + return 0; + } + + public long GetResult(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + + return 0; + } + + //GetSystemEventReadableHandles() -> (KObject, KObject) + public long GetSystemEventReadableHandles(ServiceCtx Context) + { + //FIXME: Is this supposed to return 2 events? + int Handle = Context.Process.HandleTable.OpenHandle(Event); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + + return 0; + } + + public long Cancel(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + + return 0; + } + + public long Submit(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + + return 0; + } + + public long SetConnectionConfirmationOption(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNifm, "Stubbed."); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + Event.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nifm/IStaticService.cs b/Ryujinx.HLE/OsHle/Services/Nifm/IStaticService.cs new file mode 100644 index 00000000..c6d773f5 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nifm/IStaticService.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Nifm +{ + class IStaticService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IStaticService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 4, CreateGeneralServiceOld }, + { 5, CreateGeneralService } + }; + } + + public long CreateGeneralServiceOld(ServiceCtx Context) + { + MakeObject(Context, new IGeneralService()); + + return 0; + } + + public long CreateGeneralService(ServiceCtx Context) + { + MakeObject(Context, new IGeneralService()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ns/IAddOnContentManager.cs b/Ryujinx.HLE/OsHle/Services/Ns/IAddOnContentManager.cs new file mode 100644 index 00000000..f3e7146e --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Ns/IAddOnContentManager.cs @@ -0,0 +1,42 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Ns +{ + class IAddOnContentManager : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IAddOnContentManager() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 2, CountAddOnContent }, + { 3, ListAddOnContent } + }; + } + + public static long CountAddOnContent(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + Context.Ns.Log.PrintStub(LogClass.ServiceNs, "Stubbed."); + + return 0; + } + + public static long ListAddOnContent(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNs, "Stubbed."); + + //TODO: This is supposed to write a u32 array aswell. + //It's unknown what it contains. + Context.ResponseData.Write(0); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ns/IServiceGetterInterface.cs b/Ryujinx.HLE/OsHle/Services/Ns/IServiceGetterInterface.cs new file mode 100644 index 00000000..3650f8a4 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Ns/IServiceGetterInterface.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Ns +{ + class IServiceGetterInterface : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IServiceGetterInterface() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ns/ISystemUpdateInterface.cs b/Ryujinx.HLE/OsHle/Services/Ns/ISystemUpdateInterface.cs new file mode 100644 index 00000000..adb6add9 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Ns/ISystemUpdateInterface.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Ns +{ + class ISystemUpdateInterface : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISystemUpdateInterface() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs b/Ryujinx.HLE/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs new file mode 100644 index 00000000..6a2c3d3b --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Ns/IVulnerabilityManagerInterface.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Ns +{ + class IVulnerabilityManagerInterface : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IVulnerabilityManagerInterface() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/OsHle/Services/Nv/INvDrvServices.cs new file mode 100644 index 00000000..5c1748bd --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/INvDrvServices.cs @@ -0,0 +1,228 @@ +using ChocolArm64.Memory; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS; +using Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu; +using Ryujinx.HLE.OsHle.Services.Nv.NvHostChannel; +using Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl; +using Ryujinx.HLE.OsHle.Services.Nv.NvMap; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Nv +{ + class INvDrvServices : IpcService, IDisposable + { + private delegate int IoctlProcessor(ServiceCtx Context, int Cmd); + + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private static Dictionary<string, IoctlProcessor> IoctlProcessors = + new Dictionary<string, IoctlProcessor>() + { + { "/dev/nvhost-as-gpu", ProcessIoctlNvGpuAS }, + { "/dev/nvhost-ctrl", ProcessIoctlNvHostCtrl }, + { "/dev/nvhost-ctrl-gpu", ProcessIoctlNvGpuGpu }, + { "/dev/nvhost-gpu", ProcessIoctlNvHostChannel }, + { "/dev/nvmap", ProcessIoctlNvMap } + }; + + public static GlobalStateTable Fds { get; private set; } + + private KEvent Event; + + public INvDrvServices() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Open }, + { 1, Ioctl }, + { 2, Close }, + { 3, Initialize }, + { 4, QueryEvent }, + { 8, SetClientPid }, + { 13, FinishInitialize } + }; + + Event = new KEvent(); + } + + static INvDrvServices() + { + Fds = new GlobalStateTable(); + } + + public long Open(ServiceCtx Context) + { + long NamePtr = Context.Request.SendBuff[0].Position; + + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr); + + int Fd = Fds.Add(Context.Process, new NvFd(Name)); + + Context.ResponseData.Write(Fd); + Context.ResponseData.Write(0); + + return 0; + } + + public long Ioctl(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + int Cmd = Context.RequestData.ReadInt32(); + + NvFd FdData = Fds.GetData<NvFd>(Context.Process, Fd); + + int Result; + + if (IoctlProcessors.TryGetValue(FdData.Name, out IoctlProcessor Process)) + { + Result = Process(Context, Cmd); + } + else + { + throw new NotImplementedException($"{FdData.Name} {Cmd:x4}"); + } + + //TODO: Verify if the error codes needs to be translated. + Context.ResponseData.Write(Result); + + return 0; + } + + public long Close(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + + Fds.Delete(Context.Process, Fd); + + Context.ResponseData.Write(0); + + return 0; + } + + public long Initialize(ServiceCtx Context) + { + long TransferMemSize = Context.RequestData.ReadInt64(); + int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0]; + + NvMapIoctl.InitializeNvMap(Context); + + Context.ResponseData.Write(0); + + return 0; + } + + public long QueryEvent(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + int EventId = Context.RequestData.ReadInt32(); + + //TODO: Use Fd/EventId, different channels have different events. + int Handle = Context.Process.HandleTable.OpenHandle(Event); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + Context.ResponseData.Write(0); + + return 0; + } + + public long SetClientPid(ServiceCtx Context) + { + long Pid = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(0); + + return 0; + } + + public long FinishInitialize(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return 0; + } + + private static int ProcessIoctlNvGpuAS(ServiceCtx Context, int Cmd) + { + return ProcessIoctl(Context, Cmd, NvGpuASIoctl.ProcessIoctl); + } + + private static int ProcessIoctlNvHostCtrl(ServiceCtx Context, int Cmd) + { + return ProcessIoctl(Context, Cmd, NvHostCtrlIoctl.ProcessIoctl); + } + + private static int ProcessIoctlNvGpuGpu(ServiceCtx Context, int Cmd) + { + return ProcessIoctl(Context, Cmd, NvGpuGpuIoctl.ProcessIoctl); + } + + private static int ProcessIoctlNvHostChannel(ServiceCtx Context, int Cmd) + { + return ProcessIoctl(Context, Cmd, NvHostChannelIoctl.ProcessIoctl); + } + + private static int ProcessIoctlNvMap(ServiceCtx Context, int Cmd) + { + return ProcessIoctl(Context, Cmd, NvMapIoctl.ProcessIoctl); + } + + private static int ProcessIoctl(ServiceCtx Context, int Cmd, IoctlProcessor Processor) + { + if (CmdIn(Cmd) && Context.Request.GetBufferType0x21().Position == 0) + { + Context.Ns.Log.PrintError(LogClass.ServiceNv, "Input buffer is null!"); + + return NvResult.InvalidInput; + } + + if (CmdOut(Cmd) && Context.Request.GetBufferType0x22().Position == 0) + { + Context.Ns.Log.PrintError(LogClass.ServiceNv, "Output buffer is null!"); + + return NvResult.InvalidInput; + } + + return Processor(Context, Cmd); + } + + private static bool CmdIn(int Cmd) + { + return ((Cmd >> 30) & 1) != 0; + } + + private static bool CmdOut(int Cmd) + { + return ((Cmd >> 31) & 1) != 0; + } + + public static void UnloadProcess(Process Process) + { + Fds.DeleteProcess(Process); + + NvGpuASIoctl.UnloadProcess(Process); + + NvHostCtrlIoctl.UnloadProcess(Process); + + NvMapIoctl.UnloadProcess(Process); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + Event.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvFd.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvFd.cs new file mode 100644 index 00000000..96fce80a --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvFd.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv +{ + class NvFd + { + public string Name { get; private set; } + + public NvFd(string Name) + { + this.Name = Name; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs new file mode 100644 index 00000000..9d955d62 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS +{ + struct NvGpuASAllocSpace + { + public int Pages; + public int PageSize; + public int Flags; + public int Padding; + public long Offset; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs new file mode 100644 index 00000000..c96c04c8 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs @@ -0,0 +1,243 @@ +using ChocolArm64.Memory; +using Ryujinx.HLE.Gpu; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Services.Nv.NvMap; +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS +{ + class NvGpuASIoctl + { + private const int FlagFixedOffset = 1; + + private static ConcurrentDictionary<Process, NvGpuVmm> Vmms; + + static NvGpuASIoctl() + { + Vmms = new ConcurrentDictionary<Process, NvGpuVmm>(); + } + + public static int ProcessIoctl(ServiceCtx Context, int Cmd) + { + switch (Cmd & 0xffff) + { + case 0x4101: return BindChannel (Context); + case 0x4102: return AllocSpace (Context); + case 0x4103: return FreeSpace (Context); + case 0x4105: return UnmapBuffer (Context); + case 0x4106: return MapBufferEx (Context); + case 0x4108: return GetVaRegions(Context); + case 0x4109: return InitializeEx(Context); + case 0x4114: return Remap (Context); + } + + throw new NotImplementedException(Cmd.ToString("x8")); + } + + private static int BindChannel(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int AllocSpace(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuASAllocSpace Args = AMemoryHelper.Read<NvGpuASAllocSpace>(Context.Memory, InputPosition); + + NvGpuVmm Vmm = GetVmm(Context); + + ulong Size = (ulong)Args.Pages * + (ulong)Args.PageSize; + + if ((Args.Flags & FlagFixedOffset) != 0) + { + Args.Offset = Vmm.Reserve(Args.Offset, (long)Size, 1); + } + else + { + Args.Offset = Vmm.Reserve((long)Size, 1); + } + + int Result = NvResult.Success; + + if (Args.Offset < 0) + { + Args.Offset = 0; + + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"No memory to allocate size {Size:x16}!"); + + Result = NvResult.OutOfMemory; + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return Result; + } + + private static int FreeSpace(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuASAllocSpace Args = AMemoryHelper.Read<NvGpuASAllocSpace>(Context.Memory, InputPosition); + + NvGpuVmm Vmm = GetVmm(Context); + + ulong Size = (ulong)Args.Pages * + (ulong)Args.PageSize; + + Vmm.Free(Args.Offset, (long)Size); + + return NvResult.Success; + } + + private static int UnmapBuffer(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuASUnmapBuffer Args = AMemoryHelper.Read<NvGpuASUnmapBuffer>(Context.Memory, InputPosition); + + NvGpuVmm Vmm = GetVmm(Context); + + if (!Vmm.Unmap(Args.Offset)) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!"); + } + + return NvResult.Success; + } + + private static int MapBufferEx(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuASMapBufferEx Args = AMemoryHelper.Read<NvGpuASMapBufferEx>(Context.Memory, InputPosition); + + NvGpuVmm Vmm = GetVmm(Context); + + NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle); + + if (Map == null) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!"); + + return NvResult.InvalidInput; + } + + long PA = Map.Address + Args.BufferOffset; + + long Size = Args.MappingSize; + + if (Size == 0) + { + Size = (uint)Map.Size; + } + + int Result = NvResult.Success; + + //Note: When the fixed offset flag is not set, + //the Offset field holds the alignment size instead. + if ((Args.Flags & FlagFixedOffset) != 0) + { + long MapEnd = Args.Offset + Args.MappingSize; + + if ((ulong)MapEnd <= (ulong)Args.Offset) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Offset 0x{Args.Offset:x16} and size 0x{Args.MappingSize:x16} results in a overflow!"); + + return NvResult.InvalidInput; + } + + if ((Args.Offset & NvGpuVmm.PageMask) != 0) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Offset 0x{Args.Offset:x16} is not page aligned!"); + + return NvResult.InvalidInput; + } + + Args.Offset = Vmm.Map(PA, Args.Offset, Size); + } + else + { + Args.Offset = Vmm.Map(PA, Size); + + if (Args.Offset < 0) + { + Args.Offset = 0; + + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"No memory to map size {Args.MappingSize:x16}!"); + + Result = NvResult.InvalidInput; + } + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return Result; + } + + private static int GetVaRegions(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int InitializeEx(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int Remap(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + + NvGpuASRemap Args = AMemoryHelper.Read<NvGpuASRemap>(Context.Memory, InputPosition); + + NvGpuVmm Vmm = GetVmm(Context); + + NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle); + + if (Map == null) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!"); + + return NvResult.InvalidInput; + } + + //FIXME: This is most likely wrong... + Vmm.Map(Map.Address, (long)(uint)Args.Offset << 16, + (long)(uint)Args.Pages << 16); + + return NvResult.Success; + } + + public static NvGpuVmm GetVmm(ServiceCtx Context) + { + return Vmms.GetOrAdd(Context.Process, (Key) => new NvGpuVmm(Context.Memory)); + } + + public static void UnloadProcess(Process Process) + { + Vmms.TryRemove(Process, out _); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs new file mode 100644 index 00000000..f3ee40b6 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS +{ + struct NvGpuASMapBufferEx + { + public int Flags; + public int Kind; + public int NvMapHandle; + public int PageSize; + public long BufferOffset; + public long MappingSize; + public long Offset; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs new file mode 100644 index 00000000..e0ccb113 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS +{ + struct NvGpuASRemap + { + public short Flags; + public short Kind; + public int NvMapHandle; + public int Padding; + public int Offset; + public int Pages; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs new file mode 100644 index 00000000..790da3c2 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS +{ + struct NvGpuASUnmapBuffer + { + public long Offset; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs new file mode 100644 index 00000000..71edebbb --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu +{ + struct NvGpuGpuGetActiveSlotMask + { + public int Slot; + public int Mask; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs new file mode 100644 index 00000000..a519fed1 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu +{ + struct NvGpuGpuGetCharacteristics + { + public long BufferSize; + public long BufferAddress; + public int Arch; + public int Impl; + public int Rev; + public int NumGpc; + public long L2CacheSize; + public long OnBoardVideoMemorySize; + public int NumTpcPerGpc; + public int BusType; + public int BigPageSize; + public int CompressionPageSize; + public int PdeCoverageBitCount; + public int AvailableBigPageSizes; + public int GpcMask; + public int SmArchSmVersion; + public int SmArchSpaVersion; + public int SmArchWarpCount; + public int GpuVaBitCount; + public int Reserved; + public long Flags; + public int TwodClass; + public int ThreedClass; + public int ComputeClass; + public int GpfifoClass; + public int InlineToMemoryClass; + public int DmaCopyClass; + public int MaxFbpsCount; + public int FbpEnMask; + public int MaxLtcPerFbp; + public int MaxLtsPerLtc; + public int MaxTexPerTpc; + public int MaxGpcCount; + public int RopL2EnMask0; + public int RopL2EnMask1; + public long ChipName; + public long GrCompbitStoreBaseHw; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs new file mode 100644 index 00000000..17a7da62 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu +{ + struct NvGpuGpuGetTpcMasks + { + public int MaskBufferSize; + public int Reserved; + public long MaskBufferAddress; + public int TpcMask; + public int Padding; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs new file mode 100644 index 00000000..c034994c --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs @@ -0,0 +1,187 @@ +using ChocolArm64.Memory; +using Ryujinx.HLE.Logging; +using System; +using System.Diagnostics; + +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu +{ + class NvGpuGpuIoctl + { + private static Stopwatch PTimer; + + private static double TicksToNs; + + static NvGpuGpuIoctl() + { + PTimer = new Stopwatch(); + + PTimer.Start(); + + TicksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000; + } + + public static int ProcessIoctl(ServiceCtx Context, int Cmd) + { + switch (Cmd & 0xffff) + { + case 0x4701: return ZcullGetCtxSize (Context); + case 0x4702: return ZcullGetInfo (Context); + case 0x4703: return ZbcSetTable (Context); + case 0x4705: return GetCharacteristics(Context); + case 0x4706: return GetTpcMasks (Context); + case 0x4714: return GetActiveSlotMask (Context); + case 0x471c: return GetGpuTime (Context); + } + + throw new NotImplementedException(Cmd.ToString("x8")); + } + + private static int ZcullGetCtxSize(ServiceCtx Context) + { + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuGpuZcullGetCtxSize Args = new NvGpuGpuZcullGetCtxSize(); + + Args.Size = 1; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int ZcullGetInfo(ServiceCtx Context) + { + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuGpuZcullGetInfo Args = new NvGpuGpuZcullGetInfo(); + + Args.WidthAlignPixels = 0x20; + Args.HeightAlignPixels = 0x20; + Args.PixelSquaresByAliquots = 0x400; + Args.AliquotTotal = 0x800; + Args.RegionByteMultiplier = 0x20; + Args.RegionHeaderSize = 0x20; + Args.SubregionHeaderSize = 0xc0; + Args.SubregionWidthAlignPixels = 0x20; + Args.SubregionHeightAlignPixels = 0x40; + Args.SubregionCount = 0x10; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int ZbcSetTable(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int GetCharacteristics(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuGpuGetCharacteristics Args = AMemoryHelper.Read<NvGpuGpuGetCharacteristics>(Context.Memory, InputPosition); + + Args.BufferSize = 0xa0; + + Args.Arch = 0x120; + Args.Impl = 0xb; + Args.Rev = 0xa1; + Args.NumGpc = 0x1; + Args.L2CacheSize = 0x40000; + Args.OnBoardVideoMemorySize = 0x0; + Args.NumTpcPerGpc = 0x2; + Args.BusType = 0x20; + Args.BigPageSize = 0x20000; + Args.CompressionPageSize = 0x20000; + Args.PdeCoverageBitCount = 0x1b; + Args.AvailableBigPageSizes = 0x30000; + Args.GpcMask = 0x1; + Args.SmArchSmVersion = 0x503; + Args.SmArchSpaVersion = 0x503; + Args.SmArchWarpCount = 0x80; + Args.GpuVaBitCount = 0x28; + Args.Reserved = 0x0; + Args.Flags = 0x55; + Args.TwodClass = 0x902d; + Args.ThreedClass = 0xb197; + Args.ComputeClass = 0xb1c0; + Args.GpfifoClass = 0xb06f; + Args.InlineToMemoryClass = 0xa140; + Args.DmaCopyClass = 0xb0b5; + Args.MaxFbpsCount = 0x1; + Args.FbpEnMask = 0x0; + Args.MaxLtcPerFbp = 0x2; + Args.MaxLtsPerLtc = 0x1; + Args.MaxTexPerTpc = 0x0; + Args.MaxGpcCount = 0x1; + Args.RopL2EnMask0 = 0x21d70; + Args.RopL2EnMask1 = 0x0; + Args.ChipName = 0x6230326d67; + Args.GrCompbitStoreBaseHw = 0x0; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int GetTpcMasks(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuGpuGetTpcMasks Args = AMemoryHelper.Read<NvGpuGpuGetTpcMasks>(Context.Memory, InputPosition); + + if (Args.MaskBufferSize != 0) + { + Args.TpcMask = 3; + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int GetActiveSlotMask(ServiceCtx Context) + { + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvGpuGpuGetActiveSlotMask Args = new NvGpuGpuGetActiveSlotMask(); + + Args.Slot = 0x07; + Args.Mask = 0x01; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int GetGpuTime(ServiceCtx Context) + { + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Memory.WriteInt64(OutputPosition, GetPTimerNanoSeconds()); + + return NvResult.Success; + } + + private static long GetPTimerNanoSeconds() + { + double Ticks = PTimer.ElapsedTicks; + + return (long)(Ticks * TicksToNs) & 0xff_ffff_ffff_ffff; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs new file mode 100644 index 00000000..21bcaceb --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu +{ + struct NvGpuGpuZcullGetCtxSize + { + public int Size; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs new file mode 100644 index 00000000..168051ed --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuGpu +{ + struct NvGpuGpuZcullGetInfo + { + public int WidthAlignPixels; + public int HeightAlignPixels; + public int PixelSquaresByAliquots; + public int AliquotTotal; + public int RegionByteMultiplier; + public int RegionHeaderSize; + public int SubregionHeaderSize; + public int SubregionWidthAlignPixels; + public int SubregionHeightAlignPixels; + public int SubregionCount; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHelper.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHelper.cs new file mode 100644 index 00000000..22f1fecc --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHelper.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv +{ + static class NvHelper + { + public static void Crash() + { + + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs new file mode 100644 index 00000000..c461fa28 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs @@ -0,0 +1,130 @@ +using ChocolArm64.Memory; +using Ryujinx.HLE.Gpu; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS; +using System; + +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostChannel +{ + class NvHostChannelIoctl + { + public static int ProcessIoctl(ServiceCtx Context, int Cmd) + { + switch (Cmd & 0xffff) + { + case 0x4714: return SetUserData (Context); + case 0x4801: return SetNvMap (Context); + case 0x4808: return SubmitGpfifo (Context); + case 0x4809: return AllocObjCtx (Context); + case 0x480b: return ZcullBind (Context); + case 0x480c: return SetErrorNotifier(Context); + case 0x480d: return SetPriority (Context); + case 0x481a: return AllocGpfifoEx2 (Context); + } + + throw new NotImplementedException(Cmd.ToString("x8")); + } + + private static int SetUserData(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int SetNvMap(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int SubmitGpfifo(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvHostChannelSubmitGpfifo Args = AMemoryHelper.Read<NvHostChannelSubmitGpfifo>(Context.Memory, InputPosition); + + NvGpuVmm Vmm = NvGpuASIoctl.GetVmm(Context); + + for (int Index = 0; Index < Args.NumEntries; Index++) + { + long Gpfifo = Context.Memory.ReadInt64(InputPosition + 0x18 + Index * 8); + + long VA = Gpfifo & 0xff_ffff_ffff; + + int Size = (int)(Gpfifo >> 40) & 0x7ffffc; + + byte[] Data = Vmm.ReadBytes(VA, Size); + + NvGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data); + + Context.Ns.Gpu.Fifo.PushBuffer(Vmm, PushBuffer); + } + + Args.SyncptId = 0; + Args.SyncptValue = 0; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int AllocObjCtx(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int ZcullBind(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int SetErrorNotifier(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int SetPriority(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int AllocGpfifoEx2(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs new file mode 100644 index 00000000..9541f701 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostChannel +{ + struct NvHostChannelSubmitGpfifo + { + public long Gpfifo; + public int NumEntries; + public int Flags; + public int SyncptId; + public int SyncptValue; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs new file mode 100644 index 00000000..a9fd9d3a --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs @@ -0,0 +1,355 @@ +using ChocolArm64.Memory; +using Ryujinx.HLE.Logging; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +{ + class NvHostCtrlIoctl + { + private static ConcurrentDictionary<Process, NvHostCtrlUserCtx> UserCtxs; + + static NvHostCtrlIoctl() + { + UserCtxs = new ConcurrentDictionary<Process, NvHostCtrlUserCtx>(); + } + + public static int ProcessIoctl(ServiceCtx Context, int Cmd) + { + switch (Cmd & 0xffff) + { + case 0x0014: return SyncptRead (Context); + case 0x0015: return SyncptIncr (Context); + case 0x0016: return SyncptWait (Context); + case 0x0019: return SyncptWaitEx (Context); + case 0x001a: return SyncptReadMax (Context); + case 0x001b: return GetConfig (Context); + case 0x001d: return EventWait (Context); + case 0x001e: return EventWaitAsync(Context); + case 0x001f: return EventRegister (Context); + } + + throw new NotImplementedException(Cmd.ToString("x8")); + } + + private static int SyncptRead(ServiceCtx Context) + { + return SyncptReadMinOrMax(Context, Max: false); + } + + private static int SyncptIncr(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + + int Id = Context.Memory.ReadInt32(InputPosition); + + if ((uint)Id >= NvHostSyncpt.SyncptsCount) + { + return NvResult.InvalidInput; + } + + GetUserCtx(Context).Syncpt.Increment(Id); + + return NvResult.Success; + } + + private static int SyncptWait(ServiceCtx Context) + { + return SyncptWait(Context, Extended: false); + } + + private static int SyncptWaitEx(ServiceCtx Context) + { + return SyncptWait(Context, Extended: true); + } + + private static int SyncptReadMax(ServiceCtx Context) + { + return SyncptReadMinOrMax(Context, Max: true); + } + + private static int GetConfig(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + string Nv = AMemoryHelper.ReadAsciiString(Context.Memory, InputPosition + 0, 0x41); + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, InputPosition + 0x41, 0x41); + + Context.Memory.WriteByte(OutputPosition + 0x82, 0); + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int EventWait(ServiceCtx Context) + { + return EventWait(Context, Async: false); + } + + private static int EventWaitAsync(ServiceCtx Context) + { + return EventWait(Context, Async: true); + } + + private static int EventRegister(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + int EventId = Context.Memory.ReadInt32(InputPosition); + + Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed."); + + return NvResult.Success; + } + + private static int SyncptReadMinOrMax(ServiceCtx Context, bool Max) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvHostCtrlSyncptRead Args = AMemoryHelper.Read<NvHostCtrlSyncptRead>(Context.Memory, InputPosition); + + if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount) + { + return NvResult.InvalidInput; + } + + if (Max) + { + Args.Value = GetUserCtx(Context).Syncpt.GetMax(Args.Id); + } + else + { + Args.Value = GetUserCtx(Context).Syncpt.GetMin(Args.Id); + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int SyncptWait(ServiceCtx Context, bool Extended) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvHostCtrlSyncptWait Args = AMemoryHelper.Read<NvHostCtrlSyncptWait>(Context.Memory, InputPosition); + + NvHostSyncpt Syncpt = GetUserCtx(Context).Syncpt; + + if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount) + { + return NvResult.InvalidInput; + } + + int Result; + + if (Syncpt.MinCompare(Args.Id, Args.Thresh)) + { + Result = NvResult.Success; + } + else if (Args.Timeout == 0) + { + Result = NvResult.TryAgain; + } + else + { + Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms..."); + + using (ManualResetEvent WaitEvent = new ManualResetEvent(false)) + { + Syncpt.AddWaiter(Args.Thresh, WaitEvent); + + //Note: Negative (> INT_MAX) timeouts aren't valid on .NET, + //in this case we just use the maximum timeout possible. + int Timeout = Args.Timeout; + + if (Timeout < -1) + { + Timeout = int.MaxValue; + } + + if (Timeout == -1) + { + WaitEvent.WaitOne(); + + Result = NvResult.Success; + } + else if (WaitEvent.WaitOne(Timeout)) + { + Result = NvResult.Success; + } + else + { + Result = NvResult.TimedOut; + } + } + + Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Resuming..."); + } + + if (Extended) + { + Context.Memory.WriteInt32(OutputPosition + 0xc, Syncpt.GetMin(Args.Id)); + } + + return Result; + } + + private static int EventWait(ServiceCtx Context, bool Async) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvHostCtrlSyncptWaitEx Args = AMemoryHelper.Read<NvHostCtrlSyncptWaitEx>(Context.Memory, InputPosition); + + if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount) + { + return NvResult.InvalidInput; + } + + void WriteArgs() + { + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + } + + NvHostSyncpt Syncpt = GetUserCtx(Context).Syncpt; + + if (Syncpt.MinCompare(Args.Id, Args.Thresh)) + { + Args.Value = Syncpt.GetMin(Args.Id); + + WriteArgs(); + + return NvResult.Success; + } + + if (!Async) + { + Args.Value = 0; + } + + if (Args.Timeout == 0) + { + WriteArgs(); + + return NvResult.TryAgain; + } + + NvHostEvent Event; + + int Result, EventIndex; + + if (Async) + { + EventIndex = Args.Value; + + if ((uint)EventIndex >= NvHostCtrlUserCtx.EventsCount) + { + return NvResult.InvalidInput; + } + + Event = GetUserCtx(Context).Events[EventIndex]; + } + else + { + Event = GetFreeEvent(Context, Syncpt, Args.Id, out EventIndex); + } + + if (Event != null && + (Event.State == NvHostEventState.Registered || + Event.State == NvHostEventState.Free)) + { + Event.Id = Args.Id; + Event.Thresh = Args.Thresh; + + Event.State = NvHostEventState.Waiting; + + if (!Async) + { + Args.Value = ((Args.Id & 0xfff) << 16) | 0x10000000; + } + else + { + Args.Value = Args.Id << 4; + } + + Args.Value |= EventIndex; + + Result = NvResult.TryAgain; + } + else + { + Result = NvResult.InvalidInput; + } + + WriteArgs(); + + return Result; + } + + private static NvHostEvent GetFreeEvent( + ServiceCtx Context, + NvHostSyncpt Syncpt, + int Id, + out int EventIndex) + { + NvHostEvent[] Events = GetUserCtx(Context).Events; + + EventIndex = NvHostCtrlUserCtx.EventsCount; + + int NullIndex = NvHostCtrlUserCtx.EventsCount; + + for (int Index = 0; Index < NvHostCtrlUserCtx.EventsCount; Index++) + { + NvHostEvent Event = Events[Index]; + + if (Event != null) + { + if (Event.State == NvHostEventState.Registered || + Event.State == NvHostEventState.Free) + { + EventIndex = Index; + + if (Event.Id == Id) + { + return Event; + } + } + } + else if (NullIndex == NvHostCtrlUserCtx.EventsCount) + { + NullIndex = Index; + } + } + + if (NullIndex < NvHostCtrlUserCtx.EventsCount) + { + EventIndex = NullIndex; + + return Events[NullIndex] = new NvHostEvent(); + } + + if (EventIndex < NvHostCtrlUserCtx.EventsCount) + { + return Events[EventIndex]; + } + + return null; + } + + public static NvHostCtrlUserCtx GetUserCtx(ServiceCtx Context) + { + return UserCtxs.GetOrAdd(Context.Process, (Key) => new NvHostCtrlUserCtx()); + } + + public static void UnloadProcess(Process Process) + { + UserCtxs.TryRemove(Process, out _); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs new file mode 100644 index 00000000..7801f467 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +{ + struct NvHostCtrlSyncptRead + { + public int Id; + public int Value; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs new file mode 100644 index 00000000..29a75dd7 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +{ + struct NvHostCtrlSyncptWait + { + public int Id; + public int Thresh; + public int Timeout; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs new file mode 100644 index 00000000..79f84214 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +{ + struct NvHostCtrlSyncptWaitEx + { + public int Id; + public int Thresh; + public int Timeout; + public int Value; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs new file mode 100644 index 00000000..5d414a2e --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +{ + class NvHostCtrlUserCtx + { + public const int LocksCount = 16; + public const int EventsCount = 64; + + public NvHostSyncpt Syncpt { get; private set; } + + public NvHostEvent[] Events { get; private set; } + + public NvHostCtrlUserCtx() + { + Syncpt = new NvHostSyncpt(); + + Events = new NvHostEvent[EventsCount]; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs new file mode 100644 index 00000000..bb294d72 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +{ + class NvHostEvent + { + public int Id; + public int Thresh; + + public NvHostEventState State; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs new file mode 100644 index 00000000..fa4583b8 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +{ + enum NvHostEventState + { + Registered = 0, + Waiting = 1, + Busy = 2, + Free = 5 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs new file mode 100644 index 00000000..47d63f79 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostCtrl +{ + class NvHostSyncpt + { + public const int SyncptsCount = 192; + + private int[] CounterMin; + private int[] CounterMax; + + private long EventMask; + + private ConcurrentDictionary<EventWaitHandle, int> Waiters; + + public NvHostSyncpt() + { + CounterMin = new int[SyncptsCount]; + CounterMax = new int[SyncptsCount]; + + Waiters = new ConcurrentDictionary<EventWaitHandle, int>(); + } + + public int GetMin(int Id) + { + return CounterMin[Id]; + } + + public int GetMax(int Id) + { + return CounterMax[Id]; + } + + public int Increment(int Id) + { + if (((EventMask >> Id) & 1) != 0) + { + Interlocked.Increment(ref CounterMax[Id]); + } + + return IncrementMin(Id); + } + + public int IncrementMin(int Id) + { + int Value = Interlocked.Increment(ref CounterMin[Id]); + + WakeUpWaiters(Id, Value); + + return Value; + } + + public int IncrementMax(int Id) + { + return Interlocked.Increment(ref CounterMax[Id]); + } + + public void AddWaiter(int Threshold, EventWaitHandle WaitEvent) + { + if (!Waiters.TryAdd(WaitEvent, Threshold)) + { + throw new InvalidOperationException(); + } + } + + public bool RemoveWaiter(EventWaitHandle WaitEvent) + { + return Waiters.TryRemove(WaitEvent, out _); + } + + private void WakeUpWaiters(int Id, int NewValue) + { + foreach (KeyValuePair<EventWaitHandle, int> KV in Waiters) + { + if (MinCompare(Id, NewValue, CounterMax[Id], KV.Value)) + { + KV.Key.Set(); + + Waiters.TryRemove(KV.Key, out _); + } + } + } + + public bool MinCompare(int Id, int Threshold) + { + return MinCompare(Id, CounterMin[Id], CounterMax[Id], Threshold); + } + + private bool MinCompare(int Id, int Min, int Max, int Threshold) + { + int MinDiff = Min - Threshold; + int MaxDiff = Max - Threshold; + + if (((EventMask >> Id) & 1) != 0) + { + return MinDiff >= 0; + } + else + { + return (uint)MaxDiff >= (uint)MinDiff; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapAlloc.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapAlloc.cs new file mode 100644 index 00000000..10634b86 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapAlloc.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + struct NvMapAlloc + { + public int Handle; + public int HeapMask; + public int Flags; + public int Align; + public long Kind; + public long Address; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapCreate.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapCreate.cs new file mode 100644 index 00000000..8f3be79d --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapCreate.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + struct NvMapCreate + { + public int Size; + public int Handle; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFree.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFree.cs new file mode 100644 index 00000000..1e13f197 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFree.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + struct NvMapFree + { + public int Handle; + public int Padding; + public long RefCount; + public int Size; + public int Flags; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFromId.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFromId.cs new file mode 100644 index 00000000..31c75131 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapFromId.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + struct NvMapFromId + { + public int Id; + public int Handle; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapGetId.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapGetId.cs new file mode 100644 index 00000000..780815d2 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapGetId.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + struct NvMapGetId + { + public int Id; + public int Handle; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandle.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandle.cs new file mode 100644 index 00000000..7902034c --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandle.cs @@ -0,0 +1,37 @@ +using System.Threading; + +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + class NvMapHandle + { + public int Handle; + public int Id; + public int Size; + public int Align; + public int Kind; + public long Address; + public bool Allocated; + + private long Dupes; + + public NvMapHandle() + { + Dupes = 1; + } + + public NvMapHandle(int Size) : this() + { + this.Size = Size; + } + + public long IncrementRefCount() + { + return Interlocked.Increment(ref Dupes); + } + + public long DecrementRefCount() + { + return Interlocked.Decrement(ref Dupes); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs new file mode 100644 index 00000000..ab1b0577 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + enum NvMapHandleParam + { + Size = 1, + Align = 2, + Base = 3, + Heap = 4, + Kind = 5, + Compr = 6 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapIoctl.cs new file mode 100644 index 00000000..376b74c1 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapIoctl.cs @@ -0,0 +1,302 @@ +using ChocolArm64.Memory; +using Ryujinx.HLE.Gpu; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Utilities; +using System.Collections.Concurrent; + +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + class NvMapIoctl + { + private const int FlagNotFreedYet = 1; + + private static ConcurrentDictionary<Process, IdDictionary> Maps; + + static NvMapIoctl() + { + Maps = new ConcurrentDictionary<Process, IdDictionary>(); + } + + public static int ProcessIoctl(ServiceCtx Context, int Cmd) + { + switch (Cmd & 0xffff) + { + case 0x0101: return Create(Context); + case 0x0103: return FromId(Context); + case 0x0104: return Alloc (Context); + case 0x0105: return Free (Context); + case 0x0109: return Param (Context); + case 0x010e: return GetId (Context); + } + + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Unsupported Ioctl command 0x{Cmd:x8}!"); + + return NvResult.NotSupported; + } + + private static int Create(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvMapCreate Args = AMemoryHelper.Read<NvMapCreate>(Context.Memory, InputPosition); + + if (Args.Size == 0) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{Args.Size:x8}!"); + + return NvResult.InvalidInput; + } + + int Size = IntUtils.RoundUp(Args.Size, NvGpuVmm.PageSize); + + Args.Handle = AddNvMap(Context, new NvMapHandle(Size)); + + Context.Ns.Log.PrintInfo(LogClass.ServiceNv, $"Created map {Args.Handle} with size 0x{Size:x8}!"); + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int FromId(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvMapFromId Args = AMemoryHelper.Read<NvMapFromId>(Context.Memory, InputPosition); + + NvMapHandle Map = GetNvMap(Context, Args.Id); + + if (Map == null) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + + return NvResult.InvalidInput; + } + + Map.IncrementRefCount(); + + Args.Handle = Args.Id; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int Alloc(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvMapAlloc Args = AMemoryHelper.Read<NvMapAlloc>(Context.Memory, InputPosition); + + NvMapHandle Map = GetNvMap(Context, Args.Handle); + + if (Map == null) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + + return NvResult.InvalidInput; + } + + if ((Args.Align & (Args.Align - 1)) != 0) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{Args.Align:x8}!"); + + return NvResult.InvalidInput; + } + + if ((uint)Args.Align < NvGpuVmm.PageSize) + { + Args.Align = NvGpuVmm.PageSize; + } + + int Result = NvResult.Success; + + if (!Map.Allocated) + { + Map.Allocated = true; + + Map.Align = Args.Align; + Map.Kind = (byte)Args.Kind; + + int Size = IntUtils.RoundUp(Map.Size, NvGpuVmm.PageSize); + + long Address = Args.Address; + + if (Address == 0) + { + //When the address is zero, we need to allocate + //our own backing memory for the NvMap. + if (!Context.Ns.Os.Allocator.TryAllocate((uint)Size, out Address)) + { + Result = NvResult.OutOfMemory; + } + } + + if (Result == NvResult.Success) + { + Map.Size = Size; + Map.Address = Address; + } + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return Result; + } + + private static int Free(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvMapFree Args = AMemoryHelper.Read<NvMapFree>(Context.Memory, InputPosition); + + NvMapHandle Map = GetNvMap(Context, Args.Handle); + + if (Map == null) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + + return NvResult.InvalidInput; + } + + long RefCount = Map.DecrementRefCount(); + + if (RefCount <= 0) + { + DeleteNvMap(Context, Args.Handle); + + Context.Ns.Log.PrintInfo(LogClass.ServiceNv, $"Deleted map {Args.Handle}!"); + + Args.Flags = 0; + } + else + { + Args.Flags = FlagNotFreedYet; + } + + Args.RefCount = RefCount; + Args.Size = Map.Size; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int Param(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvMapParam Args = AMemoryHelper.Read<NvMapParam>(Context.Memory, InputPosition); + + NvMapHandle Map = GetNvMap(Context, Args.Handle); + + if (Map == null) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + + return NvResult.InvalidInput; + } + + switch ((NvMapHandleParam)Args.Param) + { + case NvMapHandleParam.Size: Args.Result = Map.Size; break; + case NvMapHandleParam.Align: Args.Result = Map.Align; break; + case NvMapHandleParam.Heap: Args.Result = 0x40000000; break; + case NvMapHandleParam.Kind: Args.Result = Map.Kind; break; + case NvMapHandleParam.Compr: Args.Result = 0; break; + + //Note: Base is not supported and returns an error. + //Any other value also returns an error. + default: return NvResult.InvalidInput; + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int GetId(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21().Position; + long OutputPosition = Context.Request.GetBufferType0x22().Position; + + NvMapGetId Args = AMemoryHelper.Read<NvMapGetId>(Context.Memory, InputPosition); + + NvMapHandle Map = GetNvMap(Context, Args.Handle); + + if (Map == null) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!"); + + return NvResult.InvalidInput; + } + + Args.Id = Args.Handle; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private static int AddNvMap(ServiceCtx Context, NvMapHandle Map) + { + IdDictionary Dict = Maps.GetOrAdd(Context.Process, (Key) => + { + IdDictionary NewDict = new IdDictionary(); + + NewDict.Add(0, new NvMapHandle()); + + return NewDict; + }); + + return Dict.Add(Map); + } + + private static bool DeleteNvMap(ServiceCtx Context, int Handle) + { + if (Maps.TryGetValue(Context.Process, out IdDictionary Dict)) + { + return Dict.Delete(Handle) != null; + } + + return false; + } + + public static void InitializeNvMap(ServiceCtx Context) + { + IdDictionary Dict = Maps.GetOrAdd(Context.Process, (Key) =>new IdDictionary()); + + Dict.Add(0, new NvMapHandle()); + } + + public static NvMapHandle GetNvMapWithFb(ServiceCtx Context, int Handle) + { + if (Maps.TryGetValue(Context.Process, out IdDictionary Dict)) + { + return Dict.GetData<NvMapHandle>(Handle); + } + + return null; + } + + public static NvMapHandle GetNvMap(ServiceCtx Context, int Handle) + { + if (Handle != 0 && Maps.TryGetValue(Context.Process, out IdDictionary Dict)) + { + return Dict.GetData<NvMapHandle>(Handle); + } + + return null; + } + + public static void UnloadProcess(Process Process) + { + Maps.TryRemove(Process, out _); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapParam.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapParam.cs new file mode 100644 index 00000000..218cb700 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvMap/NvMapParam.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv.NvMap +{ + struct NvMapParam + { + public int Handle; + public int Param; + public int Result; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvResult.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvResult.cs new file mode 100644 index 00000000..720f5ccf --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvResult.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.OsHle.Services.Nv +{ + static class NvResult + { + public const int Success = 0; + public const int TryAgain = -11; + public const int OutOfMemory = -12; + public const int InvalidInput = -22; + public const int NotSupported = -25; + public const int Restart = -85; + public const int TimedOut = -110; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlService.cs b/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlService.cs new file mode 100644 index 00000000..eb363ade --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlService.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Pctl +{ + class IParentalControlService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IParentalControlService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlServiceFactory.cs b/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlServiceFactory.cs new file mode 100644 index 00000000..094245f6 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Pctl/IParentalControlServiceFactory.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Pctl +{ + class IParentalControlServiceFactory : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IParentalControlServiceFactory() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, CreateService } + }; + } + + public static long CreateService(ServiceCtx Context) + { + MakeObject(Context, new IParentalControlService()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs b/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs new file mode 100644 index 00000000..9f85f3d1 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs @@ -0,0 +1,61 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Pl +{ + class ISharedFontManager : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISharedFontManager() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, RequestLoad }, + { 1, GetLoadState }, + { 2, GetFontSize }, + { 3, GetSharedMemoryAddressOffset }, + { 4, GetSharedMemoryNativeHandle } + }; + } + + public long RequestLoad(ServiceCtx Context) + { + SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + + return 0; + } + + public long GetLoadState(ServiceCtx Context) + { + Context.ResponseData.Write(1); //Loaded + + return 0; + } + + public long GetFontSize(ServiceCtx Context) + { + Context.ResponseData.Write(Horizon.FontSize); + + return 0; + } + + public long GetSharedMemoryAddressOffset(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + return 0; + } + + public long GetSharedMemoryNativeHandle(ServiceCtx Context) + { + int Handle = Context.Process.HandleTable.OpenHandle(Context.Ns.Os.FontSharedMem); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs b/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs new file mode 100644 index 00000000..97fd95dc --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.OsHle.Services.Pl +{ + enum SharedFontType + { + JapanUsEurope = 0, + SimplifiedChinese = 1, + SimplifiedChineseEx = 2, + TraditionalChinese = 3, + Korean = 4, + NintendoEx = 5 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Prepo/IPrepoService.cs b/Ryujinx.HLE/OsHle/Services/Prepo/IPrepoService.cs new file mode 100644 index 00000000..f313921a --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Prepo/IPrepoService.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Prepo +{ + class IPrepoService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IPrepoService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 10101, SaveReportWithUser } + }; + } + + public static long SaveReportWithUser(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServicePrepo, "Stubbed."); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs b/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs new file mode 100644 index 00000000..0720c5d2 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs @@ -0,0 +1,162 @@ +using Ryujinx.HLE.OsHle.Services.Acc; +using Ryujinx.HLE.OsHle.Services.Am; +using Ryujinx.HLE.OsHle.Services.Apm; +using Ryujinx.HLE.OsHle.Services.Aud; +using Ryujinx.HLE.OsHle.Services.Bsd; +using Ryujinx.HLE.OsHle.Services.Caps; +using Ryujinx.HLE.OsHle.Services.Friend; +using Ryujinx.HLE.OsHle.Services.FspSrv; +using Ryujinx.HLE.OsHle.Services.Hid; +using Ryujinx.HLE.OsHle.Services.Lm; +using Ryujinx.HLE.OsHle.Services.Nfp; +using Ryujinx.HLE.OsHle.Services.Ns; +using Ryujinx.HLE.OsHle.Services.Nv; +using Ryujinx.HLE.OsHle.Services.Pctl; +using Ryujinx.HLE.OsHle.Services.Pl; +using Ryujinx.HLE.OsHle.Services.Prepo; +using Ryujinx.HLE.OsHle.Services.Set; +using Ryujinx.HLE.OsHle.Services.Sfdnsres; +using Ryujinx.HLE.OsHle.Services.Sm; +using Ryujinx.HLE.OsHle.Services.Ssl; +using Ryujinx.HLE.OsHle.Services.Vi; +using System; + +namespace Ryujinx.HLE.OsHle.Services +{ + static class ServiceFactory + { + public static IpcService MakeService(string Name) + { + switch (Name) + { + case "acc:u0": + return new IAccountServiceForApplication(); + + case "aoc:u": + return new IAddOnContentManager(); + + case "apm": + return new IManager(); + + case "apm:p": + return new IManager(); + + case "appletAE": + return new IAllSystemAppletProxiesService(); + + case "appletOE": + return new IApplicationProxyService(); + + case "audout:u": + return new IAudioOutManager(); + + case "audren:u": + return new IAudioRendererManager(); + + case "bsd:s": + return new IClient(); + + case "bsd:u": + return new IClient(); + + case "caps:a": + return new IAlbumAccessorService(); + + case "caps:ss": + return new IScreenshotService(); + + case "friend:a": + return new IServiceCreator(); + + case "friend:u": + return new IServiceCreator(); + + case "fsp-srv": + return new IFileSystemProxy(); + + case "hid": + return new IHidServer(); + + case "lm": + return new ILogService(); + + case "nfp:user": + return new IUserManager(); + + case "nifm:u": + return new Nifm.IStaticService(); + + case "ns:ec": + return new IServiceGetterInterface(); + + case "ns:su": + return new ISystemUpdateInterface(); + + case "ns:vm": + return new IVulnerabilityManagerInterface(); + + case "nvdrv": + return new INvDrvServices(); + + case "nvdrv:a": + return new INvDrvServices(); + + case "pctl:s": + return new IParentalControlServiceFactory(); + + case "pctl:r": + return new IParentalControlServiceFactory(); + + case "pctl:a": + return new IParentalControlServiceFactory(); + + case "pctl": + return new IParentalControlServiceFactory(); + + case "pl:u": + return new ISharedFontManager(); + + case "prepo:a": + return new IPrepoService(); + + case "prepo:u": + return new IPrepoService(); + + case "set": + return new ISettingsServer(); + + case "set:sys": + return new ISystemSettingsServer(); + + case "sfdnsres": + return new IResolver(); + + case "sm:": + return new IUserInterface(); + + case "ssl": + return new ISslService(); + + case "time:a": + return new Time.IStaticService(); + + case "time:s": + return new Time.IStaticService(); + + case "time:u": + return new Time.IStaticService(); + + case "vi:m": + return new IManagerRootService(); + + case "vi:s": + return new ISystemRootService(); + + case "vi:u": + return new IApplicationRootService(); + } + + throw new NotImplementedException(Name); + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Set/ISettingsServer.cs b/Ryujinx.HLE/OsHle/Services/Set/ISettingsServer.cs new file mode 100644 index 00000000..96297ad2 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Set/ISettingsServer.cs @@ -0,0 +1,60 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Set +{ + class ISettingsServer : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISettingsServer() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetLanguageCode }, + { 1, GetAvailableLanguageCodes }, + { 3, GetAvailableLanguageCodeCount } + }; + } + + public static long GetLanguageCode(ServiceCtx Context) + { + Context.ResponseData.Write(Context.Ns.Os.SystemState.DesiredLanguageCode); + + return 0; + } + + public static long GetAvailableLanguageCodes(ServiceCtx Context) + { + long Position = Context.Request.RecvListBuff[0].Position; + long Size = Context.Request.RecvListBuff[0].Size; + + int Count = (int)(Size / 8); + + if (Count > SystemStateMgr.LanguageCodes.Length) + { + Count = SystemStateMgr.LanguageCodes.Length; + } + + for (int Index = 0; Index < Count; Index++) + { + Context.Memory.WriteInt64(Position, SystemStateMgr.GetLanguageCode(Index)); + + Position += 8; + } + + Context.ResponseData.Write(Count); + + return 0; + } + + public static long GetAvailableLanguageCodeCount(ServiceCtx Context) + { + Context.ResponseData.Write(SystemStateMgr.LanguageCodes.Length); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Set/ISystemSettingsServer.cs b/Ryujinx.HLE/OsHle/Services/Set/ISystemSettingsServer.cs new file mode 100644 index 00000000..6a3ea537 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Set/ISystemSettingsServer.cs @@ -0,0 +1,149 @@ +using Ryujinx.HLE.OsHle.Ipc; +using Ryujinx.HLE.Settings; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.OsHle.Services.Set +{ + class ISystemSettingsServer : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISystemSettingsServer() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 4, GetFirmwareVersion2 }, + { 23, GetColorSetId }, + { 24, SetColorSetId }, + { 38, GetSettingsItemValue } + }; + } + + public static long GetFirmwareVersion2(ServiceCtx Context) + { + long ReplyPos = Context.Request.RecvListBuff[0].Position; + long ReplySize = Context.Request.RecvListBuff[0].Size; + + const byte MajorFWVersion = 0x03; + const byte MinorFWVersion = 0x00; + const byte MicroFWVersion = 0x00; + const byte Unknown = 0x00; //Build? + + const int RevisionNumber = 0x0A; + + const string Platform = "NX"; + const string UnknownHex = "7fbde2b0bba4d14107bf836e4643043d9f6c8e47"; + const string Version = "3.0.0"; + const string Build = "NintendoSDK Firmware for NX 3.0.0-10.0"; + + //http://switchbrew.org/index.php?title=System_Version_Title + using (MemoryStream MS = new MemoryStream(0x100)) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write(MajorFWVersion); + Writer.Write(MinorFWVersion); + Writer.Write(MicroFWVersion); + Writer.Write(Unknown); + + Writer.Write(RevisionNumber); + + Writer.Write(Encoding.ASCII.GetBytes(Platform)); + + MS.Seek(0x28, SeekOrigin.Begin); + + Writer.Write(Encoding.ASCII.GetBytes(UnknownHex)); + + MS.Seek(0x68, SeekOrigin.Begin); + + Writer.Write(Encoding.ASCII.GetBytes(Version)); + + MS.Seek(0x80, SeekOrigin.Begin); + + Writer.Write(Encoding.ASCII.GetBytes(Build)); + + Context.Memory.WriteBytes(ReplyPos, MS.ToArray()); + } + + return 0; + } + + public static long GetColorSetId(ServiceCtx Context) + { + Context.ResponseData.Write((int)Context.Ns.Settings.ThemeColor); + + return 0; + } + + public static long SetColorSetId(ServiceCtx Context) + { + int ColorSetId = Context.RequestData.ReadInt32(); + + Context.Ns.Settings.ThemeColor = (ColorSet)ColorSetId; + return 0; + } + + public static long GetSettingsItemValue(ServiceCtx Context) + { + long ClassPos = Context.Request.PtrBuff[0].Position; + long ClassSize = Context.Request.PtrBuff[0].Size; + + long NamePos = Context.Request.PtrBuff[1].Position; + long NameSize = Context.Request.PtrBuff[1].Size; + + long ReplyPos = Context.Request.ReceiveBuff[0].Position; + long ReplySize = Context.Request.ReceiveBuff[0].Size; + + byte[] Class = Context.Memory.ReadBytes(ClassPos, ClassSize); + byte[] Name = Context.Memory.ReadBytes(NamePos, NameSize); + + string AskedSetting = Encoding.ASCII.GetString(Class).Trim('\0') + "!" + Encoding.ASCII.GetString(Name).Trim('\0'); + + NxSettings.Settings.TryGetValue(AskedSetting, out object NxSetting); + + if (NxSetting != null) + { + byte[] SettingBuffer = new byte[ReplySize]; + + if (NxSetting is string StringValue) + { + if (StringValue.Length + 1 > ReplySize) + { + Context.Ns.Log.PrintError(Logging.LogClass.ServiceSet, $"{AskedSetting} String value size is too big!"); + } + else + { + SettingBuffer = Encoding.ASCII.GetBytes(StringValue + "\0"); + } + } + if (NxSetting is int IntValue) + { + SettingBuffer = BitConverter.GetBytes(IntValue); + } + else if (NxSetting is bool BoolValue) + { + SettingBuffer[0] = BoolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(NxSetting.GetType().Name); + } + + Context.Memory.WriteBytes(ReplyPos, SettingBuffer); + + Context.Ns.Log.PrintDebug(Logging.LogClass.ServiceSet, $"{AskedSetting} set value: {NxSetting} as {NxSetting.GetType()}"); + } + else + { + Context.Ns.Log.PrintError(Logging.LogClass.ServiceSet, $"{AskedSetting} not found!"); + } + + return 0; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Set/NxSettings.cs b/Ryujinx.HLE/OsHle/Services/Set/NxSettings.cs new file mode 100644 index 00000000..dc4a0b96 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Set/NxSettings.cs @@ -0,0 +1,1711 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Set +{ + static class NxSettings + { + //Generated automatically from a Switch 3.0 config file (Tid: 0100000000000818). + public static Dictionary<string, object> Settings = new Dictionary<string, object>() + { + { "account!na_required_for_network_service", true }, + { "account.daemon!background_awaking_periodicity", 10800 }, + { "account.daemon!schedule_periodicity", 3600 }, + { "account.daemon!profile_sync_interval", 18000 }, + { "account.daemon!na_info_refresh_interval", 46800 }, + { "am.display!transition_layer_enabled", true }, + { "am.gpu!gpu_scheduling_enabled", true }, + { "am.gpu!gpu_scheduling_frame_time_us", 116666 }, + { "am.gpu!gpu_scheduling_fg_app_us", 116166 }, + { "am.gpu!gpu_scheduling_bg_app_us", 104500 }, + { "am.gpu!gpu_scheduling_oa_us", 500 }, + { "am.gpu!gpu_scheduling_fg_sa_us", 11666 }, + { "am.gpu!gpu_scheduling_bg_sa_us", 0 }, + { "am.gpu!gpu_scheduling_fg_la_us", 11666 }, + { "am.gpu!gpu_scheduling_partial_fg_la_us", 2000 }, + { "am.gpu!gpu_scheduling_bg_la_us", 0 }, + { "audio!audren_log_enabled", false }, + { "audio!audout_log_enabled", false }, + { "audio!audin_log_enabled", false }, + { "audio!hwopus_log_enabled", false }, + { "audio!adsp_log_enabled", false }, + { "audio!suspend_for_debugger_enabled", false }, + { "audio!uac_speaker_enabled", false }, + { "bgtc!enable_halfawake", 1 }, + { "bgtc!enable_battery_saver", 1 }, + { "bgtc!leaving_halfawake_margin", 3 }, + { "bgtc!battery_threshold_save", 20 }, + { "bgtc!battery_threshold_stop", 20 }, + { "bgtc!minimum_interval_normal", 1800 }, + { "bgtc!minimum_interval_save", 86400 }, + { "boot!force_maintenance", false }, + { "capsrv!screenshot_layerstack", "screenshot" }, + { "capsrv!enable_album_screenshot_filedata_verification", true }, + { "devmenu!enable_application_update", true }, + { "devmenu!enable_exhibition_mode", false }, + { "eclct!analytics_override", false }, + { "eclct!analytics_pollperiod", 86400 }, + { "err!applet_auto_close", false }, + { "friends!background_processing", true }, + { "htc!disconnection_emulation", false }, + { "idle!dim_level_percent_lcd", 10 }, + { "idle!dim_level_percent_tv", 70 }, + { "lbl!force_disable_als", false }, + { "lm!enable_sd_card_logging", false }, + { "lm!sd_card_log_output_directory", "NxBinLogs" }, + { "mii!is_db_test_mode_enabled", false }, + { "news!system_version", 2 }, + { "nfp!not_locked_tag", true }, + { "nfp!play_report", false }, + { "nifm!is_communication_control_enabled_for_test", false }, + { "nifm!connection_test_timeout", 45000 }, + { "nifm!apply_config_timeout", 30000 }, + { "nifm!ethernet_adapter_standby_time", 10000 }, + { "nim.install!prefer_delta_evenif_inefficient", false }, + { "nim.install!apply_delta_stress_storage", 0 }, + { "ns.notification!retry_interval", 60 }, + { "ns.notification!enable_network_update", true }, + { "ns.notification!enable_download_task_list", true }, + { "ns.notification!enable_version_list", true }, + { "ns.notification!enable_random_wait", true }, + { "ns.notification!debug_waiting_limit", 0 }, + { "ns.notification!enable_request_on_cold_boot", true }, + { "ns.sdcard!mount_sdcard", true }, + { "ns.sdcard!compare_sdcard", 0 }, + { "ns.gamecard!mount_gamecard_result_value", 0 }, + { "ns.gamecard!try_gamecard_access_result_value", 0 }, + { "nv!00008600", "" }, + { "nv!0007b25e", "" }, + { "nv!0083e1", "" }, + { "nv!01621887", "" }, + { "nv!03134743", "" }, + { "nv!0356afd0", "" }, + { "nv!0356afd1", "" }, + { "nv!0356afd2", "" }, + { "nv!0356afd3", "" }, + { "nv!094313", "" }, + { "nv!0x04dc09", "" }, + { "nv!0x111133", "" }, + { "nv!0x1aa483", "" }, + { "nv!0x1cb1cf", "" }, + { "nv!0x1cb1d0", "" }, + { "nv!0x1e3221", "" }, + { "nv!0x300fc8", "" }, + { "nv!0x301fc8", "" }, + { "nv!0x302fc8", "" }, + { "nv!0x3eec59", "" }, + { "nv!0x46b3ed", "" }, + { "nv!0x523dc0", "" }, + { "nv!0x523dc1", "" }, + { "nv!0x523dc2", "" }, + { "nv!0x523dc3", "" }, + { "nv!0x523dc4", "" }, + { "nv!0x523dc5", "" }, + { "nv!0x523dc6", "" }, + { "nv!0x523dd0", "" }, + { "nv!0x523dd1", "" }, + { "nv!0x523dd3", "" }, + { "nv!0x5344bb", "" }, + { "nv!0x555237", "" }, + { "nv!0x58a234", "" }, + { "nv!0x7b4428", "" }, + { "nv!0x923dc0", "" }, + { "nv!0x923dc1", "" }, + { "nv!0x923dc2", "" }, + { "nv!0x923dc3", "" }, + { "nv!0x923dc4", "" }, + { "nv!0x923dd3", "" }, + { "nv!0x9abdc5", "" }, + { "nv!0x9abdc6", "" }, + { "nv!0xaaa36c", "" }, + { "nv!0xb09da0", "" }, + { "nv!0xb09da1", "" }, + { "nv!0xb09da2", "" }, + { "nv!0xb09da3", "" }, + { "nv!0xb09da4", "" }, + { "nv!0xb09da5", "" }, + { "nv!0xb0b348", "" }, + { "nv!0xb0b349", "" }, + { "nv!0xbb558f", "" }, + { "nv!0xbd10fb", "" }, + { "nv!0xc32ad3", "" }, + { "nv!0xce2348", "" }, + { "nv!0xcfd81f", "" }, + { "nv!0xe0036b", "" }, + { "nv!0xe01f2d", "" }, + { "nv!0xe17212", "" }, + { "nv!0xeae966", "" }, + { "nv!0xed4f82", "" }, + { "nv!0xf12335", "" }, + { "nv!0xf12336", "" }, + { "nv!10261989", "" }, + { "nv!1042d483", "" }, + { "nv!10572898", "" }, + { "nv!115631", "" }, + { "nv!12950094", "" }, + { "nv!1314f311", "" }, + { "nv!1314f312", "" }, + { "nv!13279512", "" }, + { "nv!13813496", "" }, + { "nv!14507179", "" }, + { "nv!15694569", "" }, + { "nv!16936964", "" }, + { "nv!17aa230c", "" }, + { "nv!182054", "" }, + { "nv!18273275", "" }, + { "nv!18273276", "" }, + { "nv!1854d03b", "" }, + { "nv!18add00d", "" }, + { "nv!19156670", "" }, + { "nv!19286545", "" }, + { "nv!1a298e9f", "" }, + { "nv!1acf43fe", "" }, + { "nv!1bda43fe", "" }, + { "nv!1c3b92", "" }, + { "nv!21509920", "" }, + { "nv!215323457", "" }, + { "nv!2165ad", "" }, + { "nv!2165ae", "" }, + { "nv!21be9c", "" }, + { "nv!233264316", "" }, + { "nv!234557580", "" }, + { "nv!23cd0e", "" }, + { "nv!24189123", "" }, + { "nv!2443266", "" }, + { "nv!25025519", "" }, + { "nv!255e39", "" }, + { "nv!2583364", "" }, + { "nv!2888c1", "" }, + { "nv!28ca3e", "" }, + { "nv!29871243", "" }, + { "nv!2a1f64", "" }, + { "nv!2dc432", "" }, + { "nv!2de437", "" }, + { "nv!2f3bb89c", "" }, + { "nv!2fd652", "" }, + { "nv!3001ac", "" }, + { "nv!31298772", "" }, + { "nv!313233", "" }, + { "nv!31f7d603", "" }, + { "nv!320ce4", "" }, + { "nv!32153248", "" }, + { "nv!32153249", "" }, + { "nv!335bca", "" }, + { "nv!342abb", "" }, + { "nv!34dfe6", "" }, + { "nv!34dfe7", "" }, + { "nv!34dfe8", "" }, + { "nv!34dfe9", "" }, + { "nv!35201578", "" }, + { "nv!359278", "" }, + { "nv!37f53a", "" }, + { "nv!38144972", "" }, + { "nv!38542646", "" }, + { "nv!3b74c9", "" }, + { "nv!3c136f", "" }, + { "nv!3cf72823", "" }, + { "nv!3d7af029", "" }, + { "nv!3ff34782", "" }, + { "nv!4129618", "" }, + { "nv!4189fac3", "" }, + { "nv!420bd4", "" }, + { "nv!42a699", "" }, + { "nv!441369", "" }, + { "nv!4458713e", "" }, + { "nv!4554b6", "" }, + { "nv!457425", "" }, + { "nv!4603b207", "" }, + { "nv!46574957", "" }, + { "nv!46574958", "" }, + { "nv!46813529", "" }, + { "nv!46f1e13d", "" }, + { "nv!47534c43", "" }, + { "nv!48550336", "" }, + { "nv!48576893", "" }, + { "nv!48576894", "" }, + { "nv!4889ac02", "" }, + { "nv!49005740", "" }, + { "nv!49867584", "" }, + { "nv!49960973", "" }, + { "nv!4a5341", "" }, + { "nv!4f4e48", "" }, + { "nv!4f8a0a", "" }, + { "nv!50299698", "" }, + { "nv!50299699", "" }, + { "nv!50361291", "" }, + { "nv!5242ae", "" }, + { "nv!53d30c", "" }, + { "nv!56347a", "" }, + { "nv!563a95f1", "" }, + { "nv!573823", "" }, + { "nv!58027529", "" }, + { "nv!5d2d63", "" }, + { "nv!5f7e3b", "" }, + { "nv!60461793", "" }, + { "nv!60d355", "" }, + { "nv!616627aa", "" }, + { "nv!62317182", "" }, + { "nv!6253fa2e", "" }, + { "nv!64100768", "" }, + { "nv!64100769", "" }, + { "nv!64100770", "" }, + { "nv!647395", "" }, + { "nv!66543234", "" }, + { "nv!67674763", "" }, + { "nv!67739784", "" }, + { "nv!68fb9c", "" }, + { "nv!69801276", "" }, + { "nv!6af9fa2f", "" }, + { "nv!6af9fa3f", "" }, + { "nv!6af9fa4f", "" }, + { "nv!6bd8c7", "" }, + { "nv!6c7691", "" }, + { "nv!6d4296ce", "" }, + { "nv!6dd7e7", "" }, + { "nv!6dd7e8", "" }, + { "nv!6fe11ec1", "" }, + { "nv!716511763", "" }, + { "nv!72504593", "" }, + { "nv!73304097", "" }, + { "nv!73314098", "" }, + { "nv!74095213", "" }, + { "nv!74095213a", "" }, + { "nv!74095213b", "" }, + { "nv!74095214", "" }, + { "nv!748f9649", "" }, + { "nv!75494732", "" }, + { "nv!78452832", "" }, + { "nv!784561", "" }, + { "nv!78e16b9c", "" }, + { "nv!79251225", "" }, + { "nv!7c128b", "" }, + { "nv!7ccd93", "" }, + { "nv!7df8d1", "" }, + { "nv!800c2310", "" }, + { "nv!80546710", "" }, + { "nv!80772310", "" }, + { "nv!808ee280", "" }, + { "nv!81131154", "" }, + { "nv!81274457", "" }, + { "nv!8292291f", "" }, + { "nv!83498426", "" }, + { "nv!84993794", "" }, + { "nv!84995585", "" }, + { "nv!84a0a0", "" }, + { "nv!852142", "" }, + { "nv!85612309", "" }, + { "nv!85612310", "" }, + { "nv!85612311", "" }, + { "nv!85612312", "" }, + { "nv!8623ff27", "" }, + { "nv!87364952", "" }, + { "nv!87f6275666", "" }, + { "nv!886748", "" }, + { "nv!89894423", "" }, + { "nv!8ad8a75", "" }, + { "nv!8ad8ad00", "" }, + { "nv!8bb815", "" }, + { "nv!8bb817", "" }, + { "nv!8bb818", "" }, + { "nv!8bb819", "" }, + { "nv!8e640cd1", "" }, + { "nv!8f34971a", "" }, + { "nv!8f773984", "" }, + { "nv!8f7a7d", "" }, + { "nv!902486209", "" }, + { "nv!90482571", "" }, + { "nv!91214835", "" }, + { "nv!912848290", "" }, + { "nv!915e56", "" }, + { "nv!92179063", "" }, + { "nv!92179064", "" }, + { "nv!92179065", "" }, + { "nv!92179066", "" }, + { "nv!92350358", "" }, + { "nv!92809063", "" }, + { "nv!92809064", "" }, + { "nv!92809065", "" }, + { "nv!92809066", "" }, + { "nv!92920143", "" }, + { "nv!93a89b12", "" }, + { "nv!93a89c0b", "" }, + { "nv!94812574", "" }, + { "nv!95282304", "" }, + { "nv!95394027", "" }, + { "nv!959b1f", "" }, + { "nv!9638af", "" }, + { "nv!96fd59", "" }, + { "nv!97f6275666", "" }, + { "nv!97f6275667", "" }, + { "nv!97f6275668", "" }, + { "nv!97f6275669", "" }, + { "nv!97f627566a", "" }, + { "nv!97f627566b", "" }, + { "nv!97f627566d", "" }, + { "nv!97f627566e", "" }, + { "nv!97f627566f", "" }, + { "nv!97f6275670", "" }, + { "nv!97f6275671", "" }, + { "nv!97f727566e", "" }, + { "nv!98480775", "" }, + { "nv!98480776", "" }, + { "nv!98480777", "" }, + { "nv!992431", "" }, + { "nv!9aa29065", "" }, + { "nv!9af32c", "" }, + { "nv!9af32d", "" }, + { "nv!9af32e", "" }, + { "nv!9c108b71", "" }, + { "nv!9f279065", "" }, + { "nv!a01bc728", "" }, + { "nv!a13b46c80", "" }, + { "nv!a22eb0", "" }, + { "nv!a2fb451e", "" }, + { "nv!a3456abe", "" }, + { "nv!a7044887", "" }, + { "nv!a7149200", "" }, + { "nv!a766215670", "" }, + { "nv!aac_drc_boost", "" }, + { "nv!aac_drc_cut", "" }, + { "nv!aac_drc_enc_target_level", "" }, + { "nv!aac_drc_heavy", "" }, + { "nv!aac_drc_reference_level", "" }, + { "nv!aalinegamma", "" }, + { "nv!aalinetweaks", "" }, + { "nv!ab34ee01", "" }, + { "nv!ab34ee02", "" }, + { "nv!ab34ee03", "" }, + { "nv!ac0274", "" }, + { "nv!af73c63e", "" }, + { "nv!af73c63f", "" }, + { "nv!af9927", "" }, + { "nv!afoverride", "" }, + { "nv!allocdeviceevents", "" }, + { "nv!applicationkey", "" }, + { "nv!appreturnonlybasicglsltype", "" }, + { "nv!app_softimage", "" }, + { "nv!app_supportbits2", "" }, + { "nv!assumetextureismipmappedatcreation", "" }, + { "nv!b1fb0f01", "" }, + { "nv!b3edd5", "" }, + { "nv!b40d9e03d", "" }, + { "nv!b7f6275666", "" }, + { "nv!b812c1", "" }, + { "nv!ba14ba1a", "" }, + { "nv!ba14ba1b", "" }, + { "nv!bd7559", "" }, + { "nv!bd755a", "" }, + { "nv!bd755c", "" }, + { "nv!bd755d", "" }, + { "nv!be58bb", "" }, + { "nv!be92cb", "" }, + { "nv!beefcba3", "" }, + { "nv!beefcba4", "" }, + { "nv!c023777f", "" }, + { "nv!c09dc8", "" }, + { "nv!c0d340", "" }, + { "nv!c2ff374c", "" }, + { "nv!c5e9d7a3", "" }, + { "nv!c5e9d7a4", "" }, + { "nv!c5e9d7b4", "" }, + { "nv!c618f9", "" }, + { "nv!ca345840", "" }, + { "nv!cachedisable", "" }, + { "nv!cast.on", "" }, + { "nv!cde", "" }, + { "nv!channelpriorityoverride", "" }, + { "nv!cleardatastorevidmem", "" }, + { "nv!cmdbufmemoryspaceenables", "" }, + { "nv!cmdbufminwords", "" }, + { "nv!cmdbufsizewords", "" }, + { "nv!conformantblitframebufferscissor", "" }, + { "nv!conformantincompletetextures", "" }, + { "nv!copybuffermethod", "" }, + { "nv!cubemapaniso", "" }, + { "nv!cubemapfiltering", "" }, + { "nv!d0e9a4d7", "" }, + { "nv!d13733f12", "" }, + { "nv!d1b399", "" }, + { "nv!d2983c32", "" }, + { "nv!d2983c33", "" }, + { "nv!d2e71b", "" }, + { "nv!d377dc", "" }, + { "nv!d377dd", "" }, + { "nv!d489f4", "" }, + { "nv!d4bce1", "" }, + { "nv!d518cb", "" }, + { "nv!d518cd", "" }, + { "nv!d518ce", "" }, + { "nv!d518d0", "" }, + { "nv!d518d1", "" }, + { "nv!d518d2", "" }, + { "nv!d518d3", "" }, + { "nv!d518d4", "" }, + { "nv!d518d5", "" }, + { "nv!d59eda", "" }, + { "nv!d83cbd", "" }, + { "nv!d8e777", "" }, + { "nv!debug_level", "" }, + { "nv!debug_mask", "" }, + { "nv!debug_options", "" }, + { "nv!devshmpageableallocations", "" }, + { "nv!df1f9812", "" }, + { "nv!df783c", "" }, + { "nv!diagenable", "" }, + { "nv!disallowcemask", "" }, + { "nv!disallowz16", "" }, + { "nv!dlmemoryspaceenables", "" }, + { "nv!e0bfec", "" }, + { "nv!e433456d", "" }, + { "nv!e435563f", "" }, + { "nv!e4cd9c", "" }, + { "nv!e5c972", "" }, + { "nv!e639ef", "" }, + { "nv!e802af", "" }, + { "nv!eae964", "" }, + { "nv!earlytexturehwallocation", "" }, + { "nv!eb92a3", "" }, + { "nv!ebca56", "" }, + { "nv!enable-noaud", "" }, + { "nv!enable-noavs", "" }, + { "nv!enable-prof", "" }, + { "nv!enable-sxesmode", "" }, + { "nv!enable-ulld", "" }, + { "nv!expert_detail_level", "" }, + { "nv!expert_output_mask", "" }, + { "nv!expert_report_mask", "" }, + { "nv!extensionstringnvarch", "" }, + { "nv!extensionstringversion", "" }, + { "nv!f00f1938", "" }, + { "nv!f10736", "" }, + { "nv!f1846870", "" }, + { "nv!f33bc370", "" }, + { "nv!f392a874", "" }, + { "nv!f49ae8", "" }, + { "nv!fa345cce", "" }, + { "nv!fa35cc4", "" }, + { "nv!faa14a", "" }, + { "nv!faf8a723", "" }, + { "nv!fastgs", "" }, + { "nv!fbf4ac45", "" }, + { "nv!fbo_blit_ignore_srgb", "" }, + { "nv!fc64c7", "" }, + { "nv!ff54ec97", "" }, + { "nv!ff54ec98", "" }, + { "nv!forceexitprocessdetach", "" }, + { "nv!forcerequestedesversion", "" }, + { "nv!__gl_", "" }, + { "nv!__gl_00008600", "" }, + { "nv!__gl_0007b25e", "" }, + { "nv!__gl_0083e1", "" }, + { "nv!__gl_01621887", "" }, + { "nv!__gl_03134743", "" }, + { "nv!__gl_0356afd0", "" }, + { "nv!__gl_0356afd1", "" }, + { "nv!__gl_0356afd2", "" }, + { "nv!__gl_0356afd3", "" }, + { "nv!__gl_094313", "" }, + { "nv!__gl_0x04dc09", "" }, + { "nv!__gl_0x111133", "" }, + { "nv!__gl_0x1aa483", "" }, + { "nv!__gl_0x1cb1cf", "" }, + { "nv!__gl_0x1cb1d0", "" }, + { "nv!__gl_0x1e3221", "" }, + { "nv!__gl_0x300fc8", "" }, + { "nv!__gl_0x301fc8", "" }, + { "nv!__gl_0x302fc8", "" }, + { "nv!__gl_0x3eec59", "" }, + { "nv!__gl_0x46b3ed", "" }, + { "nv!__gl_0x523dc0", "" }, + { "nv!__gl_0x523dc1", "" }, + { "nv!__gl_0x523dc2", "" }, + { "nv!__gl_0x523dc3", "" }, + { "nv!__gl_0x523dc4", "" }, + { "nv!__gl_0x523dc5", "" }, + { "nv!__gl_0x523dc6", "" }, + { "nv!__gl_0x523dd0", "" }, + { "nv!__gl_0x523dd1", "" }, + { "nv!__gl_0x523dd3", "" }, + { "nv!__gl_0x5344bb", "" }, + { "nv!__gl_0x555237", "" }, + { "nv!__gl_0x58a234", "" }, + { "nv!__gl_0x7b4428", "" }, + { "nv!__gl_0x923dc0", "" }, + { "nv!__gl_0x923dc1", "" }, + { "nv!__gl_0x923dc2", "" }, + { "nv!__gl_0x923dc3", "" }, + { "nv!__gl_0x923dc4", "" }, + { "nv!__gl_0x923dd3", "" }, + { "nv!__gl_0x9abdc5", "" }, + { "nv!__gl_0x9abdc6", "" }, + { "nv!__gl_0xaaa36c", "" }, + { "nv!__gl_0xb09da0", "" }, + { "nv!__gl_0xb09da1", "" }, + { "nv!__gl_0xb09da2", "" }, + { "nv!__gl_0xb09da3", "" }, + { "nv!__gl_0xb09da4", "" }, + { "nv!__gl_0xb09da5", "" }, + { "nv!__gl_0xb0b348", "" }, + { "nv!__gl_0xb0b349", "" }, + { "nv!__gl_0xbb558f", "" }, + { "nv!__gl_0xbd10fb", "" }, + { "nv!__gl_0xc32ad3", "" }, + { "nv!__gl_0xce2348", "" }, + { "nv!__gl_0xcfd81f", "" }, + { "nv!__gl_0xe0036b", "" }, + { "nv!__gl_0xe01f2d", "" }, + { "nv!__gl_0xe17212", "" }, + { "nv!__gl_0xeae966", "" }, + { "nv!__gl_0xed4f82", "" }, + { "nv!__gl_0xf12335", "" }, + { "nv!__gl_0xf12336", "" }, + { "nv!__gl_10261989", "" }, + { "nv!__gl_1042d483", "" }, + { "nv!__gl_10572898", "" }, + { "nv!__gl_115631", "" }, + { "nv!__gl_12950094", "" }, + { "nv!__gl_1314f311", "" }, + { "nv!__gl_1314f312", "" }, + { "nv!__gl_13279512", "" }, + { "nv!__gl_13813496", "" }, + { "nv!__gl_14507179", "" }, + { "nv!__gl_15694569", "" }, + { "nv!__gl_16936964", "" }, + { "nv!__gl_17aa230c", "" }, + { "nv!__gl_182054", "" }, + { "nv!__gl_18273275", "" }, + { "nv!__gl_18273276", "" }, + { "nv!__gl_1854d03b", "" }, + { "nv!__gl_18add00d", "" }, + { "nv!__gl_19156670", "" }, + { "nv!__gl_19286545", "" }, + { "nv!__gl_1a298e9f", "" }, + { "nv!__gl_1acf43fe", "" }, + { "nv!__gl_1bda43fe", "" }, + { "nv!__gl_1c3b92", "" }, + { "nv!__gl_21509920", "" }, + { "nv!__gl_215323457", "" }, + { "nv!__gl_2165ad", "" }, + { "nv!__gl_2165ae", "" }, + { "nv!__gl_21be9c", "" }, + { "nv!__gl_233264316", "" }, + { "nv!__gl_234557580", "" }, + { "nv!__gl_23cd0e", "" }, + { "nv!__gl_24189123", "" }, + { "nv!__gl_2443266", "" }, + { "nv!__gl_25025519", "" }, + { "nv!__gl_255e39", "" }, + { "nv!__gl_2583364", "" }, + { "nv!__gl_2888c1", "" }, + { "nv!__gl_28ca3e", "" }, + { "nv!__gl_29871243", "" }, + { "nv!__gl_2a1f64", "" }, + { "nv!__gl_2dc432", "" }, + { "nv!__gl_2de437", "" }, + { "nv!__gl_2f3bb89c", "" }, + { "nv!__gl_2fd652", "" }, + { "nv!__gl_3001ac", "" }, + { "nv!__gl_31298772", "" }, + { "nv!__gl_313233", "" }, + { "nv!__gl_31f7d603", "" }, + { "nv!__gl_320ce4", "" }, + { "nv!__gl_32153248", "" }, + { "nv!__gl_32153249", "" }, + { "nv!__gl_335bca", "" }, + { "nv!__gl_342abb", "" }, + { "nv!__gl_34dfe6", "" }, + { "nv!__gl_34dfe7", "" }, + { "nv!__gl_34dfe8", "" }, + { "nv!__gl_34dfe9", "" }, + { "nv!__gl_35201578", "" }, + { "nv!__gl_359278", "" }, + { "nv!__gl_37f53a", "" }, + { "nv!__gl_38144972", "" }, + { "nv!__gl_38542646", "" }, + { "nv!__gl_3b74c9", "" }, + { "nv!__gl_3c136f", "" }, + { "nv!__gl_3cf72823", "" }, + { "nv!__gl_3d7af029", "" }, + { "nv!__gl_3ff34782", "" }, + { "nv!__gl_4129618", "" }, + { "nv!__gl_4189fac3", "" }, + { "nv!__gl_420bd4", "" }, + { "nv!__gl_42a699", "" }, + { "nv!__gl_441369", "" }, + { "nv!__gl_4458713e", "" }, + { "nv!__gl_4554b6", "" }, + { "nv!__gl_457425", "" }, + { "nv!__gl_4603b207", "" }, + { "nv!__gl_46574957", "" }, + { "nv!__gl_46574958", "" }, + { "nv!__gl_46813529", "" }, + { "nv!__gl_46f1e13d", "" }, + { "nv!__gl_47534c43", "" }, + { "nv!__gl_48550336", "" }, + { "nv!__gl_48576893", "" }, + { "nv!__gl_48576894", "" }, + { "nv!__gl_4889ac02", "" }, + { "nv!__gl_49005740", "" }, + { "nv!__gl_49867584", "" }, + { "nv!__gl_49960973", "" }, + { "nv!__gl_4a5341", "" }, + { "nv!__gl_4f4e48", "" }, + { "nv!__gl_4f8a0a", "" }, + { "nv!__gl_50299698", "" }, + { "nv!__gl_50299699", "" }, + { "nv!__gl_50361291", "" }, + { "nv!__gl_5242ae", "" }, + { "nv!__gl_53d30c", "" }, + { "nv!__gl_56347a", "" }, + { "nv!__gl_563a95f1", "" }, + { "nv!__gl_573823", "" }, + { "nv!__gl_58027529", "" }, + { "nv!__gl_5d2d63", "" }, + { "nv!__gl_5f7e3b", "" }, + { "nv!__gl_60461793", "" }, + { "nv!__gl_60d355", "" }, + { "nv!__gl_616627aa", "" }, + { "nv!__gl_62317182", "" }, + { "nv!__gl_6253fa2e", "" }, + { "nv!__gl_64100768", "" }, + { "nv!__gl_64100769", "" }, + { "nv!__gl_64100770", "" }, + { "nv!__gl_647395", "" }, + { "nv!__gl_66543234", "" }, + { "nv!__gl_67674763", "" }, + { "nv!__gl_67739784", "" }, + { "nv!__gl_68fb9c", "" }, + { "nv!__gl_69801276", "" }, + { "nv!__gl_6af9fa2f", "" }, + { "nv!__gl_6af9fa3f", "" }, + { "nv!__gl_6af9fa4f", "" }, + { "nv!__gl_6bd8c7", "" }, + { "nv!__gl_6c7691", "" }, + { "nv!__gl_6d4296ce", "" }, + { "nv!__gl_6dd7e7", "" }, + { "nv!__gl_6dd7e8", "" }, + { "nv!__gl_6fe11ec1", "" }, + { "nv!__gl_716511763", "" }, + { "nv!__gl_72504593", "" }, + { "nv!__gl_73304097", "" }, + { "nv!__gl_73314098", "" }, + { "nv!__gl_74095213", "" }, + { "nv!__gl_74095213a", "" }, + { "nv!__gl_74095213b", "" }, + { "nv!__gl_74095214", "" }, + { "nv!__gl_748f9649", "" }, + { "nv!__gl_75494732", "" }, + { "nv!__gl_78452832", "" }, + { "nv!__gl_784561", "" }, + { "nv!__gl_78e16b9c", "" }, + { "nv!__gl_79251225", "" }, + { "nv!__gl_7c128b", "" }, + { "nv!__gl_7ccd93", "" }, + { "nv!__gl_7df8d1", "" }, + { "nv!__gl_800c2310", "" }, + { "nv!__gl_80546710", "" }, + { "nv!__gl_80772310", "" }, + { "nv!__gl_808ee280", "" }, + { "nv!__gl_81131154", "" }, + { "nv!__gl_81274457", "" }, + { "nv!__gl_8292291f", "" }, + { "nv!__gl_83498426", "" }, + { "nv!__gl_84993794", "" }, + { "nv!__gl_84995585", "" }, + { "nv!__gl_84a0a0", "" }, + { "nv!__gl_852142", "" }, + { "nv!__gl_85612309", "" }, + { "nv!__gl_85612310", "" }, + { "nv!__gl_85612311", "" }, + { "nv!__gl_85612312", "" }, + { "nv!__gl_8623ff27", "" }, + { "nv!__gl_87364952", "" }, + { "nv!__gl_87f6275666", "" }, + { "nv!__gl_886748", "" }, + { "nv!__gl_89894423", "" }, + { "nv!__gl_8ad8a75", "" }, + { "nv!__gl_8ad8ad00", "" }, + { "nv!__gl_8bb815", "" }, + { "nv!__gl_8bb817", "" }, + { "nv!__gl_8bb818", "" }, + { "nv!__gl_8bb819", "" }, + { "nv!__gl_8e640cd1", "" }, + { "nv!__gl_8f34971a", "" }, + { "nv!__gl_8f773984", "" }, + { "nv!__gl_8f7a7d", "" }, + { "nv!__gl_902486209", "" }, + { "nv!__gl_90482571", "" }, + { "nv!__gl_91214835", "" }, + { "nv!__gl_912848290", "" }, + { "nv!__gl_915e56", "" }, + { "nv!__gl_92179063", "" }, + { "nv!__gl_92179064", "" }, + { "nv!__gl_92179065", "" }, + { "nv!__gl_92179066", "" }, + { "nv!__gl_92350358", "" }, + { "nv!__gl_92809063", "" }, + { "nv!__gl_92809064", "" }, + { "nv!__gl_92809065", "" }, + { "nv!__gl_92809066", "" }, + { "nv!__gl_92920143", "" }, + { "nv!__gl_93a89b12", "" }, + { "nv!__gl_93a89c0b", "" }, + { "nv!__gl_94812574", "" }, + { "nv!__gl_95282304", "" }, + { "nv!__gl_95394027", "" }, + { "nv!__gl_959b1f", "" }, + { "nv!__gl_9638af", "" }, + { "nv!__gl_96fd59", "" }, + { "nv!__gl_97f6275666", "" }, + { "nv!__gl_97f6275667", "" }, + { "nv!__gl_97f6275668", "" }, + { "nv!__gl_97f6275669", "" }, + { "nv!__gl_97f627566a", "" }, + { "nv!__gl_97f627566b", "" }, + { "nv!__gl_97f627566d", "" }, + { "nv!__gl_97f627566e", "" }, + { "nv!__gl_97f627566f", "" }, + { "nv!__gl_97f6275670", "" }, + { "nv!__gl_97f6275671", "" }, + { "nv!__gl_97f727566e", "" }, + { "nv!__gl_98480775", "" }, + { "nv!__gl_98480776", "" }, + { "nv!__gl_98480777", "" }, + { "nv!__gl_992431", "" }, + { "nv!__gl_9aa29065", "" }, + { "nv!__gl_9af32c", "" }, + { "nv!__gl_9af32d", "" }, + { "nv!__gl_9af32e", "" }, + { "nv!__gl_9c108b71", "" }, + { "nv!__gl_9f279065", "" }, + { "nv!__gl_a01bc728", "" }, + { "nv!__gl_a13b46c80", "" }, + { "nv!__gl_a22eb0", "" }, + { "nv!__gl_a2fb451e", "" }, + { "nv!__gl_a3456abe", "" }, + { "nv!__gl_a7044887", "" }, + { "nv!__gl_a7149200", "" }, + { "nv!__gl_a766215670", "" }, + { "nv!__gl_aalinegamma", "" }, + { "nv!__gl_aalinetweaks", "" }, + { "nv!__gl_ab34ee01", "" }, + { "nv!__gl_ab34ee02", "" }, + { "nv!__gl_ab34ee03", "" }, + { "nv!__gl_ac0274", "" }, + { "nv!__gl_af73c63e", "" }, + { "nv!__gl_af73c63f", "" }, + { "nv!__gl_af9927", "" }, + { "nv!__gl_afoverride", "" }, + { "nv!__gl_allocdeviceevents", "" }, + { "nv!__gl_applicationkey", "" }, + { "nv!__gl_appreturnonlybasicglsltype", "" }, + { "nv!__gl_app_softimage", "" }, + { "nv!__gl_app_supportbits2", "" }, + { "nv!__gl_assumetextureismipmappedatcreation", "" }, + { "nv!__gl_b1fb0f01", "" }, + { "nv!__gl_b3edd5", "" }, + { "nv!__gl_b40d9e03d", "" }, + { "nv!__gl_b7f6275666", "" }, + { "nv!__gl_b812c1", "" }, + { "nv!__gl_ba14ba1a", "" }, + { "nv!__gl_ba14ba1b", "" }, + { "nv!__gl_bd7559", "" }, + { "nv!__gl_bd755a", "" }, + { "nv!__gl_bd755c", "" }, + { "nv!__gl_bd755d", "" }, + { "nv!__gl_be58bb", "" }, + { "nv!__gl_be92cb", "" }, + { "nv!__gl_beefcba3", "" }, + { "nv!__gl_beefcba4", "" }, + { "nv!__gl_c023777f", "" }, + { "nv!__gl_c09dc8", "" }, + { "nv!__gl_c0d340", "" }, + { "nv!__gl_c2ff374c", "" }, + { "nv!__gl_c5e9d7a3", "" }, + { "nv!__gl_c5e9d7a4", "" }, + { "nv!__gl_c5e9d7b4", "" }, + { "nv!__gl_c618f9", "" }, + { "nv!__gl_ca345840", "" }, + { "nv!__gl_cachedisable", "" }, + { "nv!__gl_channelpriorityoverride", "" }, + { "nv!__gl_cleardatastorevidmem", "" }, + { "nv!__gl_cmdbufmemoryspaceenables", "" }, + { "nv!__gl_cmdbufminwords", "" }, + { "nv!__gl_cmdbufsizewords", "" }, + { "nv!__gl_conformantblitframebufferscissor", "" }, + { "nv!__gl_conformantincompletetextures", "" }, + { "nv!__gl_copybuffermethod", "" }, + { "nv!__gl_cubemapaniso", "" }, + { "nv!__gl_cubemapfiltering", "" }, + { "nv!__gl_d0e9a4d7", "" }, + { "nv!__gl_d13733f12", "" }, + { "nv!__gl_d1b399", "" }, + { "nv!__gl_d2983c32", "" }, + { "nv!__gl_d2983c33", "" }, + { "nv!__gl_d2e71b", "" }, + { "nv!__gl_d377dc", "" }, + { "nv!__gl_d377dd", "" }, + { "nv!__gl_d489f4", "" }, + { "nv!__gl_d4bce1", "" }, + { "nv!__gl_d518cb", "" }, + { "nv!__gl_d518cd", "" }, + { "nv!__gl_d518ce", "" }, + { "nv!__gl_d518d0", "" }, + { "nv!__gl_d518d1", "" }, + { "nv!__gl_d518d2", "" }, + { "nv!__gl_d518d3", "" }, + { "nv!__gl_d518d4", "" }, + { "nv!__gl_d518d5", "" }, + { "nv!__gl_d59eda", "" }, + { "nv!__gl_d83cbd", "" }, + { "nv!__gl_d8e777", "" }, + { "nv!__gl_debug_level", "" }, + { "nv!__gl_debug_mask", "" }, + { "nv!__gl_debug_options", "" }, + { "nv!__gl_devshmpageableallocations", "" }, + { "nv!__gl_df1f9812", "" }, + { "nv!__gl_df783c", "" }, + { "nv!__gl_diagenable", "" }, + { "nv!__gl_disallowcemask", "" }, + { "nv!__gl_disallowz16", "" }, + { "nv!__gl_dlmemoryspaceenables", "" }, + { "nv!__gl_e0bfec", "" }, + { "nv!__gl_e433456d", "" }, + { "nv!__gl_e435563f", "" }, + { "nv!__gl_e4cd9c", "" }, + { "nv!__gl_e5c972", "" }, + { "nv!__gl_e639ef", "" }, + { "nv!__gl_e802af", "" }, + { "nv!__gl_eae964", "" }, + { "nv!__gl_earlytexturehwallocation", "" }, + { "nv!__gl_eb92a3", "" }, + { "nv!__gl_ebca56", "" }, + { "nv!__gl_expert_detail_level", "" }, + { "nv!__gl_expert_output_mask", "" }, + { "nv!__gl_expert_report_mask", "" }, + { "nv!__gl_extensionstringnvarch", "" }, + { "nv!__gl_extensionstringversion", "" }, + { "nv!__gl_f00f1938", "" }, + { "nv!__gl_f10736", "" }, + { "nv!__gl_f1846870", "" }, + { "nv!__gl_f33bc370", "" }, + { "nv!__gl_f392a874", "" }, + { "nv!__gl_f49ae8", "" }, + { "nv!__gl_fa345cce", "" }, + { "nv!__gl_fa35cc4", "" }, + { "nv!__gl_faa14a", "" }, + { "nv!__gl_faf8a723", "" }, + { "nv!__gl_fastgs", "" }, + { "nv!__gl_fbf4ac45", "" }, + { "nv!__gl_fbo_blit_ignore_srgb", "" }, + { "nv!__gl_fc64c7", "" }, + { "nv!__gl_ff54ec97", "" }, + { "nv!__gl_ff54ec98", "" }, + { "nv!__gl_forceexitprocessdetach", "" }, + { "nv!__gl_forcerequestedesversion", "" }, + { "nv!__gl_glsynctovblank", "" }, + { "nv!__gl_gvitimeoutcontrol", "" }, + { "nv!__gl_hcctrl", "" }, + { "nv!__gl_hwstate_per_ctx", "" }, + { "nv!__gl_machinecachelimit", "" }, + { "nv!__gl_maxframesallowed", "" }, + { "nv!__gl_memmgrcachedalloclimit", "" }, + { "nv!__gl_memmgrcachedalloclimitratio", "" }, + { "nv!__gl_memmgrsysheapalloclimit", "" }, + { "nv!__gl_memmgrsysheapalloclimitratio", "" }, + { "nv!__gl_memmgrvidheapalloclimit", "" }, + { "nv!__gl_mosaic_clip_to_subdev", "" }, + { "nv!__gl_mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!__gl_mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!__gl_overlaymergeblittimerms", "" }, + { "nv!__gl_perfmon_mode", "" }, + { "nv!__gl_pixbar_mode", "" }, + { "nv!__gl_qualityenhancements", "" }, + { "nv!__gl_r27s18q28", "" }, + { "nv!__gl_r2d7c1d8", "" }, + { "nv!__gl_renderer", "" }, + { "nv!__gl_renderqualityflags", "" }, + { "nv!__gl_s3tcquality", "" }, + { "nv!__gl_shaderatomics", "" }, + { "nv!__gl_shadercacheinitsize", "" }, + { "nv!__gl_shader_disk_cache_path", "" }, + { "nv!__gl_shader_disk_cache_read_only", "" }, + { "nv!__gl_shaderobjects", "" }, + { "nv!__gl_shaderportabilitywarnings", "" }, + { "nv!__gl_shaderwarningsaserrors", "" }, + { "nv!__gl_skiptexturehostcopies", "" }, + { "nv!__glslc_debug_level", "" }, + { "nv!__glslc_debug_mask", "" }, + { "nv!__glslc_debug_options", "" }, + { "nv!__glslc_debug_filename", "" }, + { "nv!__gl_sli_dli_control", "" }, + { "nv!__gl_sparsetexture", "" }, + { "nv!__gl_spinlooptimeout", "" }, + { "nv!__gl_sync_to_vblank", "" }, + { "nv!glsynctovblank", "" }, + { "nv!__gl_sysheapreuseratio", "" }, + { "nv!__gl_sysmemtexturepromotion", "" }, + { "nv!__gl_targetflushcount", "" }, + { "nv!__gl_tearingfreeswappresent", "" }, + { "nv!__gl_texclampbehavior", "" }, + { "nv!__gl_texlodbias", "" }, + { "nv!__gl_texmemoryspaceenables", "" }, + { "nv!__gl_textureprecache", "" }, + { "nv!__gl_threadcontrol", "" }, + { "nv!__gl_threadcontrol2", "" }, + { "nv!__gl_usegvievents", "" }, + { "nv!__gl_vbomemoryspaceenables", "" }, + { "nv!__gl_vertexlimit", "" }, + { "nv!__gl_vidheapreuseratio", "" }, + { "nv!__gl_vpipe", "" }, + { "nv!__gl_vpipeformatbloatlimit", "" }, + { "nv!__gl_wglmessageboxonabort", "" }, + { "nv!__gl_writeinfolog", "" }, + { "nv!__gl_writeprogramobjectassembly", "" }, + { "nv!__gl_writeprogramobjectsource", "" }, + { "nv!__gl_xnvadapterpresent", "" }, + { "nv!__gl_yield", "" }, + { "nv!__gl_yieldfunction", "" }, + { "nv!__gl_yieldfunctionfast", "" }, + { "nv!__gl_yieldfunctionslow", "" }, + { "nv!__gl_yieldfunctionwaitfordcqueue", "" }, + { "nv!__gl_yieldfunctionwaitforframe", "" }, + { "nv!__gl_yieldfunctionwaitforgpu", "" }, + { "nv!__gl_zbctableaddhysteresis", "" }, + { "nv!gpu_debug_mode", "" }, + { "nv!gpu_stay_on", "" }, + { "nv!gpu_timeout_ms_max", "" }, + { "nv!gvitimeoutcontrol", "" }, + { "nv!hcctrl", "" }, + { "nv!hwstate_per_ctx", "" }, + { "nv!libandroid_enable_log", "" }, + { "nv!machinecachelimit", "" }, + { "nv!maxframesallowed", "" }, + { "nv!media.aac_51_output_enabled", "" }, + { "nv!memmgrcachedalloclimit", "" }, + { "nv!memmgrcachedalloclimitratio", "" }, + { "nv!memmgrsysheapalloclimit", "" }, + { "nv!memmgrsysheapalloclimitratio", "" }, + { "nv!memmgrvidheapalloclimit", "" }, + { "nv!mosaic_clip_to_subdev", "" }, + { "nv!mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!nvblit.dump", "" }, + { "nv!nvblit.profile", "" }, + { "nv!nvblit.twod", "" }, + { "nv!nvblit.vic", "" }, + { "nv!nvddk_vic_prevent_use", "" }, + { "nv!nv_decompression", "" }, + { "nv!nvdisp_bl_ctrl", "0" }, + { "nv!nvdisp_debug_mask", "" }, + { "nv!nvdisp_enable_ts", "0" }, + { "nv!nvhdcp_timeout_ms", "12000" }, + { "nv!nvhdcp_max_retries", "5" }, + { "nv!nv_emc_dvfs_test", "" }, + { "nv!nv_emc_init_rate_hz", "" }, + { "nv!nv_gmmu_va_page_split", "" }, + { "nv!nv_gmmu_va_range", "" }, + { "nv!nvhost_debug_mask", "" }, + { "nv!nvidia.hwc.dump_config", "" }, + { "nv!nvidia.hwc.dump_layerlist", "" }, + { "nv!nvidia.hwc.dump_windows", "" }, + { "nv!nvidia.hwc.enable_disp_trans", "" }, + { "nv!nvidia.hwc.ftrace_enable", "" }, + { "nv!nvidia.hwc.hdcp_enable", "" }, + { "nv!nvidia.hwc.hidden_window_mask0", "" }, + { "nv!nvidia.hwc.hidden_window_mask1", "" }, + { "nv!nvidia.hwc.immediate_modeset", "" }, + { "nv!nvidia.hwc.imp_enable", "" }, + { "nv!nvidia.hwc.no_egl", "" }, + { "nv!nvidia.hwc.no_scratchblit", "" }, + { "nv!nvidia.hwc.no_vic", "" }, + { "nv!nvidia.hwc.null_display", "" }, + { "nv!nvidia.hwc.scan_props", "" }, + { "nv!nvidia.hwc.swap_interval", "" }, + { "nv!nvidia.hwc.war_1515812", "0" }, + { "nv!nvmap_debug_mask", "" }, + { "nv!nv_memory_profiler", "" }, + { "nv!nvnflinger_enable_log", "" }, + { "nv!nvnflinger_flip_policy", "" }, + { "nv!nvnflinger_hotplug_autoswitch", "0" }, + { "nv!nvnflinger_prefer_primary_layer", "0" }, + { "nv!nvnflinger_service_priority", "" }, + { "nv!nvnflinger_service_threads", "" }, + { "nv!nvnflinger_swap_interval", "" }, + { "nv!nvnflinger_track_perf", "" }, + { "nv!nvnflinger_virtualdisplay_policy", "60hz" }, + { "nv!nvn_no_vsync_capability", false }, + { "nv!nvn_through_opengl", "" }, + { "nv!nv_pllcx_always_on", "" }, + { "nv!nv_pllcx_safe_div", "" }, + { "nv!nvrm_gpu_channel_interleave", "" }, + { "nv!nvrm_gpu_channel_priority", "" }, + { "nv!nvrm_gpu_channel_timeslice", "" }, + { "nv!nvrm_gpu_default_device_index", "" }, + { "nv!nvrm_gpu_dummy", "" }, + { "nv!nvrm_gpu_help", "" }, + { "nv!nvrm_gpu_nvgpu_disable", "" }, + { "nv!nvrm_gpu_nvgpu_do_nfa_partial_map", "" }, + { "nv!nvrm_gpu_nvgpu_ecc_overrides", "" }, + { "nv!nvrm_gpu_nvgpu_no_as_get_va_regions", "" }, + { "nv!nvrm_gpu_nvgpu_no_channel_abort", "" }, + { "nv!nvrm_gpu_nvgpu_no_cyclestats", "" }, + { "nv!nvrm_gpu_nvgpu_no_fixed", "" }, + { "nv!nvrm_gpu_nvgpu_no_gpu_characteristics", "" }, + { "nv!nvrm_gpu_nvgpu_no_ioctl_mutex", "" }, + { "nv!nvrm_gpu_nvgpu_no_map_buffer_ex", "" }, + { "nv!nvrm_gpu_nvgpu_no_robustness", "" }, + { "nv!nvrm_gpu_nvgpu_no_sparse", "" }, + { "nv!nvrm_gpu_nvgpu_no_syncpoints", "" }, + { "nv!nvrm_gpu_nvgpu_no_tsg", "" }, + { "nv!nvrm_gpu_nvgpu_no_zbc", "" }, + { "nv!nvrm_gpu_nvgpu_no_zcull", "" }, + { "nv!nvrm_gpu_nvgpu_wrap_channels_in_tsgs", "" }, + { "nv!nvrm_gpu_prevent_use", "" }, + { "nv!nvrm_gpu_trace", "" }, + { "nv!nvsched_debug_mask", "" }, + { "nv!nvsched_force_enable", "" }, + { "nv!nvsched_force_log", "" }, + { "nv!nv_usb_plls_hw_ctrl", "" }, + { "nv!nv_winsys", "" }, + { "nv!nvwsi_dump", "" }, + { "nv!nvwsi_fill", "" }, + { "nv!ogl_", "" }, + { "nv!ogl_0356afd0", "" }, + { "nv!ogl_0356afd1", "" }, + { "nv!ogl_0356afd2", "" }, + { "nv!ogl_0356afd3", "" }, + { "nv!ogl_0x923dc0", "" }, + { "nv!ogl_0x923dc1", "" }, + { "nv!ogl_0x923dc2", "" }, + { "nv!ogl_0x923dc3", "" }, + { "nv!ogl_0x923dc4", "" }, + { "nv!ogl_0x923dd3", "" }, + { "nv!ogl_0x9abdc5", "" }, + { "nv!ogl_0x9abdc6", "" }, + { "nv!ogl_0xbd10fb", "" }, + { "nv!ogl_0xce2348", "" }, + { "nv!ogl_10261989", "" }, + { "nv!ogl_1042d483", "" }, + { "nv!ogl_10572898", "" }, + { "nv!ogl_115631", "" }, + { "nv!ogl_12950094", "" }, + { "nv!ogl_1314f311", "" }, + { "nv!ogl_1314f312", "" }, + { "nv!ogl_13279512", "" }, + { "nv!ogl_13813496", "" }, + { "nv!ogl_14507179", "" }, + { "nv!ogl_15694569", "" }, + { "nv!ogl_16936964", "" }, + { "nv!ogl_17aa230c", "" }, + { "nv!ogl_182054", "" }, + { "nv!ogl_18273275", "" }, + { "nv!ogl_18273276", "" }, + { "nv!ogl_1854d03b", "" }, + { "nv!ogl_18add00d", "" }, + { "nv!ogl_19156670", "" }, + { "nv!ogl_19286545", "" }, + { "nv!ogl_1a298e9f", "" }, + { "nv!ogl_1acf43fe", "" }, + { "nv!ogl_1bda43fe", "" }, + { "nv!ogl_1c3b92", "" }, + { "nv!ogl_21509920", "" }, + { "nv!ogl_215323457", "" }, + { "nv!ogl_2165ad", "" }, + { "nv!ogl_2165ae", "" }, + { "nv!ogl_21be9c", "" }, + { "nv!ogl_233264316", "" }, + { "nv!ogl_234557580", "" }, + { "nv!ogl_23cd0e", "" }, + { "nv!ogl_24189123", "" }, + { "nv!ogl_2443266", "" }, + { "nv!ogl_25025519", "" }, + { "nv!ogl_255e39", "" }, + { "nv!ogl_2583364", "" }, + { "nv!ogl_2888c1", "" }, + { "nv!ogl_28ca3e", "" }, + { "nv!ogl_29871243", "" }, + { "nv!ogl_2a1f64", "" }, + { "nv!ogl_2dc432", "" }, + { "nv!ogl_2de437", "" }, + { "nv!ogl_2f3bb89c", "" }, + { "nv!ogl_2fd652", "" }, + { "nv!ogl_3001ac", "" }, + { "nv!ogl_31298772", "" }, + { "nv!ogl_313233", "" }, + { "nv!ogl_31f7d603", "" }, + { "nv!ogl_320ce4", "" }, + { "nv!ogl_32153248", "" }, + { "nv!ogl_32153249", "" }, + { "nv!ogl_335bca", "" }, + { "nv!ogl_342abb", "" }, + { "nv!ogl_34dfe6", "" }, + { "nv!ogl_34dfe7", "" }, + { "nv!ogl_34dfe8", "" }, + { "nv!ogl_34dfe9", "" }, + { "nv!ogl_35201578", "" }, + { "nv!ogl_359278", "" }, + { "nv!ogl_37f53a", "" }, + { "nv!ogl_38144972", "" }, + { "nv!ogl_38542646", "" }, + { "nv!ogl_3b74c9", "" }, + { "nv!ogl_3c136f", "" }, + { "nv!ogl_3cf72823", "" }, + { "nv!ogl_3d7af029", "" }, + { "nv!ogl_3ff34782", "" }, + { "nv!ogl_4129618", "" }, + { "nv!ogl_4189fac3", "" }, + { "nv!ogl_420bd4", "" }, + { "nv!ogl_42a699", "" }, + { "nv!ogl_441369", "" }, + { "nv!ogl_4458713e", "" }, + { "nv!ogl_4554b6", "" }, + { "nv!ogl_457425", "" }, + { "nv!ogl_4603b207", "" }, + { "nv!ogl_46574957", "" }, + { "nv!ogl_46574958", "" }, + { "nv!ogl_46813529", "" }, + { "nv!ogl_46f1e13d", "" }, + { "nv!ogl_47534c43", "" }, + { "nv!ogl_48550336", "" }, + { "nv!ogl_48576893", "" }, + { "nv!ogl_48576894", "" }, + { "nv!ogl_4889ac02", "" }, + { "nv!ogl_49005740", "" }, + { "nv!ogl_49867584", "" }, + { "nv!ogl_49960973", "" }, + { "nv!ogl_4a5341", "" }, + { "nv!ogl_4f4e48", "" }, + { "nv!ogl_4f8a0a", "" }, + { "nv!ogl_50299698", "" }, + { "nv!ogl_50299699", "" }, + { "nv!ogl_50361291", "" }, + { "nv!ogl_5242ae", "" }, + { "nv!ogl_53d30c", "" }, + { "nv!ogl_56347a", "" }, + { "nv!ogl_563a95f1", "" }, + { "nv!ogl_573823", "" }, + { "nv!ogl_58027529", "" }, + { "nv!ogl_5d2d63", "" }, + { "nv!ogl_5f7e3b", "" }, + { "nv!ogl_60461793", "" }, + { "nv!ogl_60d355", "" }, + { "nv!ogl_616627aa", "" }, + { "nv!ogl_62317182", "" }, + { "nv!ogl_6253fa2e", "" }, + { "nv!ogl_64100768", "" }, + { "nv!ogl_64100769", "" }, + { "nv!ogl_64100770", "" }, + { "nv!ogl_647395", "" }, + { "nv!ogl_66543234", "" }, + { "nv!ogl_67674763", "" }, + { "nv!ogl_67739784", "" }, + { "nv!ogl_68fb9c", "" }, + { "nv!ogl_69801276", "" }, + { "nv!ogl_6af9fa2f", "" }, + { "nv!ogl_6af9fa3f", "" }, + { "nv!ogl_6af9fa4f", "" }, + { "nv!ogl_6bd8c7", "" }, + { "nv!ogl_6c7691", "" }, + { "nv!ogl_6d4296ce", "" }, + { "nv!ogl_6dd7e7", "" }, + { "nv!ogl_6dd7e8", "" }, + { "nv!ogl_6fe11ec1", "" }, + { "nv!ogl_716511763", "" }, + { "nv!ogl_72504593", "" }, + { "nv!ogl_73304097", "" }, + { "nv!ogl_73314098", "" }, + { "nv!ogl_74095213", "" }, + { "nv!ogl_74095213a", "" }, + { "nv!ogl_74095213b", "" }, + { "nv!ogl_74095214", "" }, + { "nv!ogl_748f9649", "" }, + { "nv!ogl_75494732", "" }, + { "nv!ogl_78452832", "" }, + { "nv!ogl_784561", "" }, + { "nv!ogl_78e16b9c", "" }, + { "nv!ogl_79251225", "" }, + { "nv!ogl_7c128b", "" }, + { "nv!ogl_7ccd93", "" }, + { "nv!ogl_7df8d1", "" }, + { "nv!ogl_800c2310", "" }, + { "nv!ogl_80546710", "" }, + { "nv!ogl_80772310", "" }, + { "nv!ogl_808ee280", "" }, + { "nv!ogl_81131154", "" }, + { "nv!ogl_81274457", "" }, + { "nv!ogl_8292291f", "" }, + { "nv!ogl_83498426", "" }, + { "nv!ogl_84993794", "" }, + { "nv!ogl_84995585", "" }, + { "nv!ogl_84a0a0", "" }, + { "nv!ogl_852142", "" }, + { "nv!ogl_85612309", "" }, + { "nv!ogl_85612310", "" }, + { "nv!ogl_85612311", "" }, + { "nv!ogl_85612312", "" }, + { "nv!ogl_8623ff27", "" }, + { "nv!ogl_87364952", "" }, + { "nv!ogl_87f6275666", "" }, + { "nv!ogl_886748", "" }, + { "nv!ogl_89894423", "" }, + { "nv!ogl_8ad8a75", "" }, + { "nv!ogl_8ad8ad00", "" }, + { "nv!ogl_8bb815", "" }, + { "nv!ogl_8bb817", "" }, + { "nv!ogl_8bb818", "" }, + { "nv!ogl_8bb819", "" }, + { "nv!ogl_8e640cd1", "" }, + { "nv!ogl_8f34971a", "" }, + { "nv!ogl_8f773984", "" }, + { "nv!ogl_8f7a7d", "" }, + { "nv!ogl_902486209", "" }, + { "nv!ogl_90482571", "" }, + { "nv!ogl_91214835", "" }, + { "nv!ogl_912848290", "" }, + { "nv!ogl_915e56", "" }, + { "nv!ogl_92179063", "" }, + { "nv!ogl_92179064", "" }, + { "nv!ogl_92179065", "" }, + { "nv!ogl_92179066", "" }, + { "nv!ogl_92350358", "" }, + { "nv!ogl_92809063", "" }, + { "nv!ogl_92809064", "" }, + { "nv!ogl_92809065", "" }, + { "nv!ogl_92809066", "" }, + { "nv!ogl_92920143", "" }, + { "nv!ogl_93a89b12", "" }, + { "nv!ogl_93a89c0b", "" }, + { "nv!ogl_94812574", "" }, + { "nv!ogl_95282304", "" }, + { "nv!ogl_95394027", "" }, + { "nv!ogl_959b1f", "" }, + { "nv!ogl_9638af", "" }, + { "nv!ogl_96fd59", "" }, + { "nv!ogl_97f6275666", "" }, + { "nv!ogl_97f6275667", "" }, + { "nv!ogl_97f6275668", "" }, + { "nv!ogl_97f6275669", "" }, + { "nv!ogl_97f627566a", "" }, + { "nv!ogl_97f627566b", "" }, + { "nv!ogl_97f627566d", "" }, + { "nv!ogl_97f627566e", "" }, + { "nv!ogl_97f627566f", "" }, + { "nv!ogl_97f6275670", "" }, + { "nv!ogl_97f6275671", "" }, + { "nv!ogl_97f727566e", "" }, + { "nv!ogl_98480775", "" }, + { "nv!ogl_98480776", "" }, + { "nv!ogl_98480777", "" }, + { "nv!ogl_992431", "" }, + { "nv!ogl_9aa29065", "" }, + { "nv!ogl_9af32c", "" }, + { "nv!ogl_9af32d", "" }, + { "nv!ogl_9af32e", "" }, + { "nv!ogl_9c108b71", "" }, + { "nv!ogl_9f279065", "" }, + { "nv!ogl_a01bc728", "" }, + { "nv!ogl_a13b46c80", "" }, + { "nv!ogl_a22eb0", "" }, + { "nv!ogl_a2fb451e", "" }, + { "nv!ogl_a3456abe", "" }, + { "nv!ogl_a7044887", "" }, + { "nv!ogl_a7149200", "" }, + { "nv!ogl_a766215670", "" }, + { "nv!ogl_aalinegamma", "" }, + { "nv!ogl_aalinetweaks", "" }, + { "nv!ogl_ab34ee01", "" }, + { "nv!ogl_ab34ee02", "" }, + { "nv!ogl_ab34ee03", "" }, + { "nv!ogl_ac0274", "" }, + { "nv!ogl_af73c63e", "" }, + { "nv!ogl_af73c63f", "" }, + { "nv!ogl_af9927", "" }, + { "nv!ogl_afoverride", "" }, + { "nv!ogl_allocdeviceevents", "" }, + { "nv!ogl_applicationkey", "" }, + { "nv!ogl_appreturnonlybasicglsltype", "" }, + { "nv!ogl_app_softimage", "" }, + { "nv!ogl_app_supportbits2", "" }, + { "nv!ogl_assumetextureismipmappedatcreation", "" }, + { "nv!ogl_b1fb0f01", "" }, + { "nv!ogl_b3edd5", "" }, + { "nv!ogl_b40d9e03d", "" }, + { "nv!ogl_b7f6275666", "" }, + { "nv!ogl_b812c1", "" }, + { "nv!ogl_ba14ba1a", "" }, + { "nv!ogl_ba14ba1b", "" }, + { "nv!ogl_bd7559", "" }, + { "nv!ogl_bd755a", "" }, + { "nv!ogl_bd755c", "" }, + { "nv!ogl_bd755d", "" }, + { "nv!ogl_be58bb", "" }, + { "nv!ogl_be92cb", "" }, + { "nv!ogl_beefcba3", "" }, + { "nv!ogl_beefcba4", "" }, + { "nv!ogl_c023777f", "" }, + { "nv!ogl_c09dc8", "" }, + { "nv!ogl_c0d340", "" }, + { "nv!ogl_c2ff374c", "" }, + { "nv!ogl_c5e9d7a3", "" }, + { "nv!ogl_c5e9d7a4", "" }, + { "nv!ogl_c5e9d7b4", "" }, + { "nv!ogl_c618f9", "" }, + { "nv!ogl_ca345840", "" }, + { "nv!ogl_cachedisable", "" }, + { "nv!ogl_channelpriorityoverride", "" }, + { "nv!ogl_cleardatastorevidmem", "" }, + { "nv!ogl_cmdbufmemoryspaceenables", "" }, + { "nv!ogl_cmdbufminwords", "" }, + { "nv!ogl_cmdbufsizewords", "" }, + { "nv!ogl_conformantblitframebufferscissor", "" }, + { "nv!ogl_conformantincompletetextures", "" }, + { "nv!ogl_copybuffermethod", "" }, + { "nv!ogl_cubemapaniso", "" }, + { "nv!ogl_cubemapfiltering", "" }, + { "nv!ogl_d0e9a4d7", "" }, + { "nv!ogl_d13733f12", "" }, + { "nv!ogl_d1b399", "" }, + { "nv!ogl_d2983c32", "" }, + { "nv!ogl_d2983c33", "" }, + { "nv!ogl_d2e71b", "" }, + { "nv!ogl_d377dc", "" }, + { "nv!ogl_d377dd", "" }, + { "nv!ogl_d489f4", "" }, + { "nv!ogl_d4bce1", "" }, + { "nv!ogl_d518cb", "" }, + { "nv!ogl_d518cd", "" }, + { "nv!ogl_d518ce", "" }, + { "nv!ogl_d518d0", "" }, + { "nv!ogl_d518d1", "" }, + { "nv!ogl_d518d2", "" }, + { "nv!ogl_d518d3", "" }, + { "nv!ogl_d518d4", "" }, + { "nv!ogl_d518d5", "" }, + { "nv!ogl_d59eda", "" }, + { "nv!ogl_d83cbd", "" }, + { "nv!ogl_d8e777", "" }, + { "nv!ogl_debug_level", "" }, + { "nv!ogl_debug_mask", "" }, + { "nv!ogl_debug_options", "" }, + { "nv!ogl_devshmpageableallocations", "" }, + { "nv!ogl_df1f9812", "" }, + { "nv!ogl_df783c", "" }, + { "nv!ogl_diagenable", "" }, + { "nv!ogl_disallowcemask", "" }, + { "nv!ogl_disallowz16", "" }, + { "nv!ogl_dlmemoryspaceenables", "" }, + { "nv!ogl_e0bfec", "" }, + { "nv!ogl_e433456d", "" }, + { "nv!ogl_e435563f", "" }, + { "nv!ogl_e4cd9c", "" }, + { "nv!ogl_e5c972", "" }, + { "nv!ogl_e639ef", "" }, + { "nv!ogl_e802af", "" }, + { "nv!ogl_eae964", "" }, + { "nv!ogl_earlytexturehwallocation", "" }, + { "nv!ogl_eb92a3", "" }, + { "nv!ogl_ebca56", "" }, + { "nv!ogl_expert_detail_level", "" }, + { "nv!ogl_expert_output_mask", "" }, + { "nv!ogl_expert_report_mask", "" }, + { "nv!ogl_extensionstringnvarch", "" }, + { "nv!ogl_extensionstringversion", "" }, + { "nv!ogl_f00f1938", "" }, + { "nv!ogl_f10736", "" }, + { "nv!ogl_f1846870", "" }, + { "nv!ogl_f33bc370", "" }, + { "nv!ogl_f392a874", "" }, + { "nv!ogl_f49ae8", "" }, + { "nv!ogl_fa345cce", "" }, + { "nv!ogl_fa35cc4", "" }, + { "nv!ogl_faa14a", "" }, + { "nv!ogl_faf8a723", "" }, + { "nv!ogl_fastgs", "" }, + { "nv!ogl_fbf4ac45", "" }, + { "nv!ogl_fbo_blit_ignore_srgb", "" }, + { "nv!ogl_fc64c7", "" }, + { "nv!ogl_ff54ec97", "" }, + { "nv!ogl_ff54ec98", "" }, + { "nv!ogl_forceexitprocessdetach", "" }, + { "nv!ogl_forcerequestedesversion", "" }, + { "nv!ogl_glsynctovblank", "" }, + { "nv!ogl_gvitimeoutcontrol", "" }, + { "nv!ogl_hcctrl", "" }, + { "nv!ogl_hwstate_per_ctx", "" }, + { "nv!ogl_machinecachelimit", "" }, + { "nv!ogl_maxframesallowed", "" }, + { "nv!ogl_memmgrcachedalloclimit", "" }, + { "nv!ogl_memmgrcachedalloclimitratio", "" }, + { "nv!ogl_memmgrsysheapalloclimit", "" }, + { "nv!ogl_memmgrsysheapalloclimitratio", "" }, + { "nv!ogl_memmgrvidheapalloclimit", "" }, + { "nv!ogl_mosaic_clip_to_subdev", "" }, + { "nv!ogl_mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!ogl_mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!ogl_overlaymergeblittimerms", "" }, + { "nv!ogl_perfmon_mode", "" }, + { "nv!ogl_pixbar_mode", "" }, + { "nv!ogl_qualityenhancements", "" }, + { "nv!ogl_r27s18q28", "" }, + { "nv!ogl_r2d7c1d8", "" }, + { "nv!ogl_renderer", "" }, + { "nv!ogl_renderqualityflags", "" }, + { "nv!ogl_s3tcquality", "" }, + { "nv!ogl_shaderatomics", "" }, + { "nv!ogl_shadercacheinitsize", "" }, + { "nv!ogl_shader_disk_cache_path", "" }, + { "nv!ogl_shader_disk_cache_read_only", "" }, + { "nv!ogl_shaderobjects", "" }, + { "nv!ogl_shaderportabilitywarnings", "" }, + { "nv!ogl_shaderwarningsaserrors", "" }, + { "nv!ogl_skiptexturehostcopies", "" }, + { "nv!ogl_sli_dli_control", "" }, + { "nv!ogl_sparsetexture", "" }, + { "nv!ogl_spinlooptimeout", "" }, + { "nv!ogl_sync_to_vblank", "" }, + { "nv!ogl_sysheapreuseratio", "" }, + { "nv!ogl_sysmemtexturepromotion", "" }, + { "nv!ogl_targetflushcount", "" }, + { "nv!ogl_tearingfreeswappresent", "" }, + { "nv!ogl_texclampbehavior", "" }, + { "nv!ogl_texlodbias", "" }, + { "nv!ogl_texmemoryspaceenables", "" }, + { "nv!ogl_textureprecache", "" }, + { "nv!ogl_threadcontrol", "" }, + { "nv!ogl_threadcontrol2", "" }, + { "nv!ogl_usegvievents", "" }, + { "nv!ogl_vbomemoryspaceenables", "" }, + { "nv!ogl_vertexlimit", "" }, + { "nv!ogl_vidheapreuseratio", "" }, + { "nv!ogl_vpipe", "" }, + { "nv!ogl_vpipeformatbloatlimit", "" }, + { "nv!ogl_wglmessageboxonabort", "" }, + { "nv!ogl_writeinfolog", "" }, + { "nv!ogl_writeprogramobjectassembly", "" }, + { "nv!ogl_writeprogramobjectsource", "" }, + { "nv!ogl_xnvadapterpresent", "" }, + { "nv!ogl_yield", "" }, + { "nv!ogl_yieldfunction", "" }, + { "nv!ogl_yieldfunctionfast", "" }, + { "nv!ogl_yieldfunctionslow", "" }, + { "nv!ogl_yieldfunctionwaitfordcqueue", "" }, + { "nv!ogl_yieldfunctionwaitforframe", "" }, + { "nv!ogl_yieldfunctionwaitforgpu", "" }, + { "nv!ogl_zbctableaddhysteresis", "" }, + { "nv!overlaymergeblittimerms", "" }, + { "nv!perfmon_mode", "" }, + { "nv!persist.sys.display.resolution", "" }, + { "nv!persist.tegra.composite.fallb", "" }, + { "nv!persist.tegra.composite.policy", "" }, + { "nv!persist.tegra.composite.range", "" }, + { "nv!persist.tegra.compositor", "" }, + { "nv!persist.tegra.compositor.virt", "" }, + { "nv!persist.tegra.compression", "" }, + { "nv!persist.tegra.cursor.enable", "" }, + { "nv!persist.tegra.didim.enable", "" }, + { "nv!persist.tegra.didim.normal", "" }, + { "nv!persist.tegra.didim.video", "" }, + { "nv!persist.tegra.disp.heads", "" }, + { "nv!persist.tegra.gamma_correction", "" }, + { "nv!persist.tegra.gpu_mapping_cache", "" }, + { "nv!persist.tegra.grlayout", "" }, + { "nv!persist.tegra.hdmi.2020.10", "" }, + { "nv!persist.tegra.hdmi.2020.fake", "" }, + { "nv!persist.tegra.hdmi.2020.force", "" }, + { "nv!persist.tegra.hdmi.autorotate", "" }, + { "nv!persist.tegra.hdmi.hdr.fake", "" }, + { "nv!persist.tegra.hdmi.ignore_ratio", "" }, + { "nv!persist.tegra.hdmi.limit.clock", "" }, + { "nv!persist.tegra.hdmi.only_16_9", "" }, + { "nv!persist.tegra.hdmi.range", "" }, + { "nv!persist.tegra.hdmi.resolution", "" }, + { "nv!persist.tegra.hdmi.underscan", "" }, + { "nv!persist.tegra.hdmi.yuv.422", "" }, + { "nv!persist.tegra.hdmi.yuv.444", "" }, + { "nv!persist.tegra.hdmi.yuv.enable", "" }, + { "nv!persist.tegra.hdmi.yuv.force", "" }, + { "nv!persist.tegra.hwc.nvdc", "" }, + { "nv!persist.tegra.idle.minimum_fps", "" }, + { "nv!persist.tegra.panel.rotation", "" }, + { "nv!persist.tegra.scan_props", "" }, + { "nv!persist.tegra.stb.mode", "" }, + { "nv!persist.tegra.zbc_override", "" }, + { "nv!pixbar_mode", "" }, + { "nv!qualityenhancements", "" }, + { "nv!r27s18q28", "" }, + { "nv!r2d7c1d8", "" }, + { "nv!renderer", "" }, + { "nv!renderqualityflags", "" }, + { "nv!rmos_debug_mask", "" }, + { "nv!rmos_set_production_mode", "" }, + { "nv!s3tcquality", "" }, + { "nv!shaderatomics", "" }, + { "nv!shadercacheinitsize", "" }, + { "nv!shader_disk_cache_path", "" }, + { "nv!shader_disk_cache_read_only", "" }, + { "nv!shaderobjects", "" }, + { "nv!shaderportabilitywarnings", "" }, + { "nv!shaderwarningsaserrors", "" }, + { "nv!skiptexturehostcopies", "" }, + { "nv!sli_dli_control", "" }, + { "nv!sparsetexture", "" }, + { "nv!spinlooptimeout", "" }, + { "nv!sync_to_vblank", "" }, + { "nv!sysheapreuseratio", "" }, + { "nv!sysmemtexturepromotion", "" }, + { "nv!targetflushcount", "" }, + { "nv!tearingfreeswappresent", "" }, + { "nv!tegra.refresh", "" }, + { "nv!texclampbehavior", "" }, + { "nv!texlodbias", "" }, + { "nv!texmemoryspaceenables", "" }, + { "nv!textureprecache", "" }, + { "nv!threadcontrol", "" }, + { "nv!threadcontrol2", "" }, + { "nv!tvmr.avp.logs", "" }, + { "nv!tvmr.buffer.logs", "" }, + { "nv!tvmr.dec.prof", "" }, + { "nv!tvmr.deint.logs", "" }, + { "nv!tvmr.dfs.logs", "" }, + { "nv!tvmr.ffprof.logs", "" }, + { "nv!tvmr.game.stream", "" }, + { "nv!tvmr.general.logs", "" }, + { "nv!tvmr.input.dump", "" }, + { "nv!tvmr.seeking.logs", "" }, + { "nv!tvmr.ts_pulldown", "" }, + { "nv!usegvievents", "" }, + { "nv!vbomemoryspaceenables", "" }, + { "nv!vcc_debug_ip", "" }, + { "nv!vcc_verbose_level", "" }, + { "nv!vertexlimit", "" }, + { "nv!viccomposer.filter", "" }, + { "nv!videostats-enable", "" }, + { "nv!vidheapreuseratio", "" }, + { "nv!vpipe", "" }, + { "nv!vpipeformatbloatlimit", "" }, + { "nv!wglmessageboxonabort", "" }, + { "nv!writeinfolog", "" }, + { "nv!writeprogramobjectassembly", "" }, + { "nv!writeprogramobjectsource", "" }, + { "nv!xnvadapterpresent", "" }, + { "nv!yield", "" }, + { "nv!yieldfunction", "" }, + { "nv!yieldfunctionfast", "" }, + { "nv!yieldfunctionslow", "" }, + { "nv!yieldfunctionwaitfordcqueue", "" }, + { "nv!yieldfunctionwaitforframe", "" }, + { "nv!yieldfunctionwaitforgpu", "" }, + { "nv!zbctableaddhysteresis", "" }, + { "pcm!enable", true }, + { "pctl!intermittent_task_interval_seconds", 21600 }, + { "prepo!devmenu_prepo_page_view", false }, + { "prepo!background_processing", true }, + { "prepo!transmission_interval_min", 10 }, + { "prepo!transmission_retry_interval", 3600 }, + { "psm!evaluation_log_enabled", false }, + { "snap_shot_dump!auto_dump", false }, + { "snap_shot_dump!output_dir", "%USERPROFILE%/Documents/Nintendo/NXDMP" }, + { "snap_shot_dump!full_dump", false }, + { "systemconfig!field_testing", false }, + { "systemconfig!exhivision", false }, + { "systempowerstate!always_reboot", false }, + { "systempowerstate!power_state_message_emulation_trigger_time", 0 }, + { "systempowerstate!power_state_message_to_emulate", 0 }, + { "target_manager!device_name", "" }, + { "vulnerability!needs_update_vulnerability_policy", 0 }, + { "apm!performance_mode_policy", "auto" }, + { "apm!sdev_throttling_enabled", true }, + { "apm!sdev_throttling_additional_delay_us", 16000 }, + { "apm!battery_draining_enabled", false }, + { "apm!sdev_cpu_overclock_enabled", false }, + { "bcat!production_mode", true }, + { "bpc!enable_quasi_off", true }, + { "bsp0!usb", "UDS" }, + { "bsp0!tm_transport", "USB" }, + { "bluetooth_debug!skip_boot", false }, + { "contents_delivery!enable_debug_api", false }, + { "eupld!upload_enabled", true }, + { "fatal!transition_to_fatal", true }, + { "fatal!show_extra_info", false }, + { "gpu_core_dump!auto_dump", false }, + { "hid_debug!enables_debugpad", false }, + { "hid_debug!manages_devices", true }, + { "hid_debug!emulate_future_device", false }, + { "hid_debug!emulate_firmware_update_failure", false }, + { "hid_debug!emulate_mcu_hardware_error", false }, + { "hid_debug!firmware_update_failure_emulation_mode", 0 }, + { "jit_debug!enable_jit_debug", false }, + { "npns!background_processing", true }, + { "npns!logmanager_redirection", true }, + { "npns!sleep_processing_timeout", 30 }, + { "npns!sleep_periodic_interval", 10800 }, + { "npns!sleep_max_try_count", 5 }, + { "npns!test_mode", false }, + { "ns.applet!overlay_applet_id", "0x010000000000100c" }, + { "ns.applet!system_applet_id", "0x0100000000001000" }, + { "ns.applet!shop_applet_id", "0x010000000000100b" }, + { "ns.autoboot!enabled", true }, + { "ns.pseudodeviceid!reset_pseudo_device_id", false }, + { "nsd!environment_identifier", "lp1" }, + { "nsd!test_mode", false }, + { "ntc!is_autonomic_correction_enabled", true }, + { "ntc!autonomic_correction_interval_seconds", 432000 }, + { "ntc!autonomic_correction_failed_retry_interval_seconds", 1800 }, + { "ntc!autonomic_correction_immediate_try_count_max", 4 }, + { "ntc!autonomic_correction_immediate_try_interval_milliseconds", 5000 }, + { "nv!nv_graphics_firmware_memory_margin", false }, + { "omm!operation_mode_policy", "auto" }, + { "omm!sleep_fade_in_ms", 50 }, + { "omm!sleep_fade_out_ms", 100 }, + { "omm!charging_sign_ms", 3000 }, + { "omm!low_battery_sign_ms", 3000 }, + { "omm!sign_fade_in_ms", 0 }, + { "omm!sign_fade_out_ms", 400 }, + { "omm!sign_wait_layer_visible_ms", 100 }, + { "omm!startup_fade_in_ms", 200 }, + { "omm!startup_fade_out_ms", 400 }, + { "omm!backlight_off_ms_on_handheld_switch", 150 }, + { "omm!sleep_on_ac_ok_boot", true }, + { "pdm!save_playlog", true }, + { "productinfo!product_name", "Nintendo Switch" }, + { "productinfo!cec_osd_name", "NintendoSwitch" }, + { "ro!ease_nro_restriction", false }, + { "settings_debug!is_debug_mode_enabled", false }, + { "systemreport!enabled", true }, + { "systemsleep!enter_sleep", true }, + { "systemsleep!enter_sc7", true }, + { "systemsleep!keep_vdd_core", true }, + { "systemsleep!disable_tma_sleep", false }, + { "systemsleep!disable_auto_sleep", false }, + { "systemsleep!override_auto_sleep_time", 0 }, + { "systemsleep!sleep_pending_time_ms", 15000 }, + { "systemsleep!hush_time_after_brief_power_button_press_ms", 1000 }, + { "systemsleep!transition_timeout_sec", 60 }, + { "systemsleep!dummy_event_auto_wake", false }, + { "systemupdate!debug_id", "0x0000000000000000" }, + { "systemupdate!debug_version", 0 }, + { "systemupdate!bgnup_retry_seconds", 60 }, + { "systemupdate!enable_background_download_stress_testing", false }, + { "systemupdate!debug_id_for_content_delivery", "0x0000000000000000" }, + { "systemupdate!debug_version_for_content_delivery", 0 }, + { "systemupdate!assumed_system_applet_version", 0 }, + { "tc!iir_filter_gain_soc", 100 }, + { "tc!iir_filter_gain_pcb", 100 }, + { "tc!tskin_soc_coefficients_handheld", "[5464, 174190]" }, + { "tc!tskin_soc_coefficients_console", "[6182, 112480]" }, + { "tc!tskin_pcb_coefficients_handheld", "[5464, 174190]" }, + { "tc!tskin_pcb_coefficients_console", "[6182, 112480]" }, + { "tc!tskin_select", "both" }, + { "tc!tskin_rate_table_handheld", "[[-1000000, 40000, 0, 0], [36000, 43000, 51, 51], [43000, 48000, 51, 102], [48000, 53000, 102, 153], [53000, 1000000, 153, 153], [48000, 1000000, 153, 153]]" }, + { "tc!tskin_rate_table_console", "[[-1000000, 43000, 51, 51], [43000, 53000, 51, 153], [53000, 58000, 153, 255], [58000, 1000000, 255, 255]]" }, + { "tc!rate_select", "both" }, + { "tc!log_enabled", false }, + { "tc!sleep_enabled", true }, + { "time!standard_steady_clock_test_offset_minutes", 0 }, + { "time!standard_steady_clock_rtc_update_interval_minutes", 5 }, + { "time!standard_network_clock_sufficient_accuracy_minutes", 43200 }, + { "usb!usb30_force_enabled", false }, + { "wlan_debug!skip_wlan_boot", false }, + }; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Sfdnsres/IResolver.cs b/Ryujinx.HLE/OsHle/Services/Sfdnsres/IResolver.cs new file mode 100644 index 00000000..2fa81eb9 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Sfdnsres/IResolver.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Sfdnsres +{ + class IResolver : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IResolver() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/OsHle/Services/Sm/IUserInterface.cs new file mode 100644 index 00000000..a0a174f5 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Sm/IUserInterface.cs @@ -0,0 +1,69 @@ +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Sm +{ + class IUserInterface : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private bool IsInitialized; + + public IUserInterface() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, Initialize }, + { 1, GetService } + }; + } + + private const int SmNotInitialized = 0x415; + + public long Initialize(ServiceCtx Context) + { + IsInitialized = true; + + return 0; + } + + public long GetService(ServiceCtx Context) + { + //Only for kernel version > 3.0.0. + if (!IsInitialized) + { + //return SmNotInitialized; + } + + string Name = string.Empty; + + for (int Index = 0; Index < 8 && + Context.RequestData.BaseStream.Position < + Context.RequestData.BaseStream.Length; Index++) + { + byte Chr = Context.RequestData.ReadByte(); + + if (Chr >= 0x20 && Chr < 0x7f) + { + Name += (char)Chr; + } + } + + if (Name == string.Empty) + { + return 0; + } + + KSession Session = new KSession(ServiceFactory.MakeService(Name), Name); + + int Handle = Context.Process.HandleTable.OpenHandle(Session); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Ssl/ISslService.cs b/Ryujinx.HLE/OsHle/Services/Ssl/ISslService.cs new file mode 100644 index 00000000..0bf4c144 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Ssl/ISslService.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Ssl +{ + class ISslService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISslService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 5, SetInterfaceVersion } + }; + } + + public long SetInterfaceVersion(ServiceCtx Context) + { + int Version = Context.RequestData.ReadInt32(); + + Context.Ns.Log.PrintStub(LogClass.ServiceSsl, "Stubbed."); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/IStaticService.cs b/Ryujinx.HLE/OsHle/Services/Time/IStaticService.cs new file mode 100644 index 00000000..1f012144 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Time/IStaticService.cs @@ -0,0 +1,60 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Time +{ + class IStaticService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IStaticService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetStandardUserSystemClock }, + { 1, GetStandardNetworkSystemClock }, + { 2, GetStandardSteadyClock }, + { 3, GetTimeZoneService }, + { 4, GetStandardLocalSystemClock } + }; + } + + public long GetStandardUserSystemClock(ServiceCtx Context) + { + MakeObject(Context, new ISystemClock(SystemClockType.User)); + + return 0; + } + + public long GetStandardNetworkSystemClock(ServiceCtx Context) + { + MakeObject(Context, new ISystemClock(SystemClockType.Network)); + + return 0; + } + + public long GetStandardSteadyClock(ServiceCtx Context) + { + MakeObject(Context, new ISteadyClock()); + + return 0; + } + + public long GetTimeZoneService(ServiceCtx Context) + { + MakeObject(Context, new ITimeZoneService()); + + return 0; + } + + public long GetStandardLocalSystemClock(ServiceCtx Context) + { + MakeObject(Context, new ISystemClock(SystemClockType.Local)); + + return 0; + } + + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/ISteadyClock.cs b/Ryujinx.HLE/OsHle/Services/Time/ISteadyClock.cs new file mode 100644 index 00000000..6be097b7 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Time/ISteadyClock.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Time +{ + class ISteadyClock : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISteadyClock() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + //... + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/ISystemClock.cs b/Ryujinx.HLE/OsHle/Services/Time/ISystemClock.cs new file mode 100644 index 00000000..787f86c2 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Time/ISystemClock.cs @@ -0,0 +1,42 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Time +{ + class ISystemClock : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private SystemClockType ClockType; + + public ISystemClock(SystemClockType ClockType) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetCurrentTime } + }; + + this.ClockType = ClockType; + } + + public long GetCurrentTime(ServiceCtx Context) + { + DateTime CurrentTime = DateTime.Now; + + if (ClockType == SystemClockType.User || + ClockType == SystemClockType.Network) + { + CurrentTime = CurrentTime.ToUniversalTime(); + } + + Context.ResponseData.Write((long)(DateTime.Now - Epoch).TotalSeconds); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs b/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs new file mode 100644 index 00000000..39454d43 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs @@ -0,0 +1,76 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Time +{ + class ITimeZoneService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public ITimeZoneService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetDeviceLocationName }, + { 101, ToCalendarTimeWithMyRule } + }; + } + + public long GetDeviceLocationName(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceTime, "Stubbed."); + + for (int Index = 0; Index < 0x24; Index++) + { + Context.ResponseData.Write((byte)0); + } + + return 0; + } + + public long ToCalendarTimeWithMyRule(ServiceCtx Context) + { + long PosixTime = Context.RequestData.ReadInt64(); + + DateTime CurrentTime = Epoch.AddSeconds(PosixTime).ToLocalTime(); + + Context.ResponseData.Write((ushort)CurrentTime.Year); + Context.ResponseData.Write((byte)CurrentTime.Month); + Context.ResponseData.Write((byte)CurrentTime.Day); + Context.ResponseData.Write((byte)CurrentTime.Hour); + Context.ResponseData.Write((byte)CurrentTime.Minute); + Context.ResponseData.Write((byte)CurrentTime.Second); + Context.ResponseData.Write((byte)0); + + /* Thanks to TuxSH + struct CalendarAdditionalInfo { + u32 tm_wday; //day of week [0,6] (Sunday = 0) + s32 tm_yday; //day of year [0,365] + struct timezone { + char[8] tz_name; + bool isDaylightSavingTime; + s32 utcOffsetSeconds; + }; + }; + */ + Context.ResponseData.Write((int)CurrentTime.DayOfWeek); + + Context.ResponseData.Write(CurrentTime.DayOfYear - 1); + + //TODO: Find out the names used. + Context.ResponseData.Write(new byte[8]); + + Context.ResponseData.Write((byte)(CurrentTime.IsDaylightSavingTime() ? 1 : 0)); + + Context.ResponseData.Write((int)TimeZoneInfo.Local.GetUtcOffset(CurrentTime).TotalSeconds); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Time/SystemClockType.cs b/Ryujinx.HLE/OsHle/Services/Time/SystemClockType.cs new file mode 100644 index 00000000..518e1eb0 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Time/SystemClockType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.OsHle.Services.Time +{ + enum SystemClockType + { + User, + Network, + Local + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/Display.cs b/Ryujinx.HLE/OsHle/Services/Vi/Display.cs new file mode 100644 index 00000000..3da51c47 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/Display.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.OsHle.Services.Vi +{ + class Display + { + public string Name { get; private set; } + + public Display(string Name) + { + this.Name = Name; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/GbpBuffer.cs b/Ryujinx.HLE/OsHle/Services/Vi/GbpBuffer.cs new file mode 100644 index 00000000..b9e9054b --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/GbpBuffer.cs @@ -0,0 +1,60 @@ +using System.IO; + +namespace Ryujinx.HLE.OsHle.Services.Android +{ + struct GbpBuffer + { + public int Magic { get; private set; } + public int Width { get; private set; } + public int Height { get; private set; } + public int Stride { get; private set; } + public int Format { get; private set; } + public int Usage { get; private set; } + + public int Pid { get; private set; } + public int RefCount { get; private set; } + + public int FdsCount { get; private set; } + public int IntsCount { get; private set; } + + public byte[] RawData { get; private set; } + + public int Size => RawData.Length + 10 * 4; + + public GbpBuffer(BinaryReader Reader) + { + Magic = Reader.ReadInt32(); + Width = Reader.ReadInt32(); + Height = Reader.ReadInt32(); + Stride = Reader.ReadInt32(); + Format = Reader.ReadInt32(); + Usage = Reader.ReadInt32(); + + Pid = Reader.ReadInt32(); + RefCount = Reader.ReadInt32(); + + FdsCount = Reader.ReadInt32(); + IntsCount = Reader.ReadInt32(); + + RawData = Reader.ReadBytes((FdsCount + IntsCount) * 4); + } + + public void Write(BinaryWriter Writer) + { + Writer.Write(Magic); + Writer.Write(Width); + Writer.Write(Height); + Writer.Write(Stride); + Writer.Write(Format); + Writer.Write(Usage); + + Writer.Write(Pid); + Writer.Write(RefCount); + + Writer.Write(FdsCount); + Writer.Write(IntsCount); + + Writer.Write(RawData); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IApplicationDisplayService.cs b/Ryujinx.HLE/OsHle/Services/Vi/IApplicationDisplayService.cs new file mode 100644 index 00000000..57848319 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/IApplicationDisplayService.cs @@ -0,0 +1,212 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; +using System.IO; + +using static Ryujinx.HLE.OsHle.Services.Android.Parcel; + +namespace Ryujinx.HLE.OsHle.Services.Vi +{ + class IApplicationDisplayService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private IdDictionary Displays; + + public IApplicationDisplayService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 100, GetRelayService }, + { 101, GetSystemDisplayService }, + { 102, GetManagerDisplayService }, + { 103, GetIndirectDisplayTransactionService }, + { 1010, OpenDisplay }, + { 1020, CloseDisplay }, + { 1102, GetDisplayResolution }, + { 2020, OpenLayer }, + { 2021, CloseLayer }, + { 2030, CreateStrayLayer }, + { 2031, DestroyStrayLayer }, + { 2101, SetLayerScalingMode }, + { 5202, GetDisplayVSyncEvent } + }; + + Displays = new IdDictionary(); + } + + public long GetRelayService(ServiceCtx Context) + { + MakeObject(Context, new IHOSBinderDriver(Context.Ns.Gpu.Renderer)); + + return 0; + } + + public long GetSystemDisplayService(ServiceCtx Context) + { + MakeObject(Context, new ISystemDisplayService()); + + return 0; + } + + public long GetManagerDisplayService(ServiceCtx Context) + { + MakeObject(Context, new IManagerDisplayService()); + + return 0; + } + + public long GetIndirectDisplayTransactionService(ServiceCtx Context) + { + MakeObject(Context, new IHOSBinderDriver(Context.Ns.Gpu.Renderer)); + + return 0; + } + + public long OpenDisplay(ServiceCtx Context) + { + string Name = GetDisplayName(Context); + + long DisplayId = Displays.Add(new Display(Name)); + + Context.ResponseData.Write(DisplayId); + + return 0; + } + + public long CloseDisplay(ServiceCtx Context) + { + int DisplayId = Context.RequestData.ReadInt32(); + + Displays.Delete(DisplayId); + + return 0; + } + + public long GetDisplayResolution(ServiceCtx Context) + { + long DisplayId = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(1280); + Context.ResponseData.Write(720); + + return 0; + } + + public long OpenLayer(ServiceCtx Context) + { + long LayerId = Context.RequestData.ReadInt64(); + long UserId = Context.RequestData.ReadInt64(); + + long ParcelPtr = Context.Request.ReceiveBuff[0].Position; + + byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr); + + Context.Memory.WriteBytes(ParcelPtr, Parcel); + + Context.ResponseData.Write((long)Parcel.Length); + + return 0; + } + + public long CloseLayer(ServiceCtx Context) + { + long LayerId = Context.RequestData.ReadInt64(); + + return 0; + } + + public long CreateStrayLayer(ServiceCtx Context) + { + long LayerFlags = Context.RequestData.ReadInt64(); + long DisplayId = Context.RequestData.ReadInt64(); + + long ParcelPtr = Context.Request.ReceiveBuff[0].Position; + + Display Disp = Displays.GetData<Display>((int)DisplayId); + + byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr); + + Context.Memory.WriteBytes(ParcelPtr, Parcel); + + Context.ResponseData.Write(0L); + Context.ResponseData.Write((long)Parcel.Length); + + return 0; + } + + public long DestroyStrayLayer(ServiceCtx Context) + { + return 0; + } + + public long SetLayerScalingMode(ServiceCtx Context) + { + int ScalingMode = Context.RequestData.ReadInt32(); + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public long GetDisplayVSyncEvent(ServiceCtx Context) + { + string Name = GetDisplayName(Context); + + int Handle = Context.Process.HandleTable.OpenHandle(Context.Ns.Os.VsyncEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + + private byte[] MakeIGraphicsBufferProducer(long BasePtr) + { + long Id = 0x20; + long CookiePtr = 0L; + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + //flat_binder_object (size is 0x28) + Writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER) + Writer.Write(0); //Flags + Writer.Write((int)(Id >> 0)); + Writer.Write((int)(Id >> 32)); + Writer.Write((int)(CookiePtr >> 0)); + Writer.Write((int)(CookiePtr >> 32)); + Writer.Write((byte)'d'); + Writer.Write((byte)'i'); + Writer.Write((byte)'s'); + Writer.Write((byte)'p'); + Writer.Write((byte)'d'); + Writer.Write((byte)'r'); + Writer.Write((byte)'v'); + Writer.Write((byte)'\0'); + Writer.Write(0L); //Pad + + return MakeParcel(MS.ToArray(), new byte[] { 0, 0, 0, 0 }); + } + } + + private string GetDisplayName(ServiceCtx Context) + { + string Name = string.Empty; + + for (int Index = 0; Index < 8 && + Context.RequestData.BaseStream.Position < + Context.RequestData.BaseStream.Length; Index++) + { + byte Chr = Context.RequestData.ReadByte(); + + if (Chr >= 0x20 && Chr < 0x7f) + { + Name += (char)Chr; + } + } + + return Name; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IApplicationRootService.cs b/Ryujinx.HLE/OsHle/Services/Vi/IApplicationRootService.cs new file mode 100644 index 00000000..93b05156 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/IApplicationRootService.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Vi +{ + class IApplicationRootService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IApplicationRootService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, GetDisplayService } + }; + } + + public long GetDisplayService(ServiceCtx Context) + { + int ServiceType = Context.RequestData.ReadInt32(); + + MakeObject(Context, new IApplicationDisplayService()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IHOSBinderDriver.cs b/Ryujinx.HLE/OsHle/Services/Vi/IHOSBinderDriver.cs new file mode 100644 index 00000000..85283b75 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/IHOSBinderDriver.cs @@ -0,0 +1,100 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Ipc; +using Ryujinx.HLE.OsHle.Services.Android; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Vi +{ + class IHOSBinderDriver : IpcService, IDisposable + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + private KEvent ReleaseEvent; + + private NvFlinger Flinger; + + public IHOSBinderDriver(IGalRenderer Renderer) + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 0, TransactParcel }, + { 1, AdjustRefcount }, + { 2, GetNativeHandle }, + { 3, TransactParcelAuto } + }; + + ReleaseEvent = new KEvent(); + + Flinger = new NvFlinger(Renderer, ReleaseEvent); + } + + public long TransactParcel(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + int Code = Context.RequestData.ReadInt32(); + + long DataPos = Context.Request.SendBuff[0].Position; + long DataSize = Context.Request.SendBuff[0].Size; + + byte[] Data = Context.Memory.ReadBytes(DataPos, DataSize); + + Data = Parcel.GetParcelData(Data); + + return Flinger.ProcessParcelRequest(Context, Data, Code); + } + + public long TransactParcelAuto(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + int Code = Context.RequestData.ReadInt32(); + + (long DataPos, long DataSize) = Context.Request.GetBufferType0x21(); + + byte[] Data = Context.Memory.ReadBytes(DataPos, DataSize); + + Data = Parcel.GetParcelData(Data); + + return Flinger.ProcessParcelRequest(Context, Data, Code); + } + + public long AdjustRefcount(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + int AddVal = Context.RequestData.ReadInt32(); + int Type = Context.RequestData.ReadInt32(); + + return 0; + } + + public long GetNativeHandle(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + uint Unk = Context.RequestData.ReadUInt32(); + + int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + ReleaseEvent.Dispose(); + + Flinger.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IManagerDisplayService.cs b/Ryujinx.HLE/OsHle/Services/Vi/IManagerDisplayService.cs new file mode 100644 index 00000000..d7a51b0e --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/IManagerDisplayService.cs @@ -0,0 +1,49 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Vi +{ + class IManagerDisplayService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IManagerDisplayService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 2010, CreateManagedLayer }, + { 2011, DestroyManagedLayer }, + { 6000, AddToLayerStack }, + { 6002, SetLayerVisibility } + }; + } + + public static long CreateManagedLayer(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + Context.ResponseData.Write(0L); //LayerId + return 0; + } + + public long DestroyManagedLayer(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + return 0; + } + + public static long AddToLayerStack(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + return 0; + } + + public static long SetLayerVisibility(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/IManagerRootService.cs b/Ryujinx.HLE/OsHle/Services/Vi/IManagerRootService.cs new file mode 100644 index 00000000..7c131dac --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/IManagerRootService.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Vi +{ + class IManagerRootService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public IManagerRootService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 2, GetDisplayService } + }; + } + + public long GetDisplayService(ServiceCtx Context) + { + int ServiceType = Context.RequestData.ReadInt32(); + + MakeObject(Context, new IApplicationDisplayService()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/ISystemDisplayService.cs b/Ryujinx.HLE/OsHle/Services/Vi/ISystemDisplayService.cs new file mode 100644 index 00000000..360268b9 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/ISystemDisplayService.cs @@ -0,0 +1,44 @@ +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Vi +{ + class ISystemDisplayService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISystemDisplayService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 2205, SetLayerZ }, + { 2207, SetLayerVisibility }, + { 3200, GetDisplayMode } + }; + } + + public static long SetLayerZ(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + return 0; + } + + public static long SetLayerVisibility(ServiceCtx Context) + { + Context.Ns.Log.PrintStub(LogClass.ServiceVi, "Stubbed."); + return 0; + } + + public static long GetDisplayMode(ServiceCtx Context) + { + Context.ResponseData.Write(1280); + Context.ResponseData.Write(720); + Context.ResponseData.Write(60.0f); + Context.ResponseData.Write(0); + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/ISystemRootService.cs b/Ryujinx.HLE/OsHle/Services/Vi/ISystemRootService.cs new file mode 100644 index 00000000..21581baa --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/ISystemRootService.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Vi +{ + class ISystemRootService : IpcService + { + private Dictionary<int, ServiceProcessRequest> m_Commands; + + public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands; + + public ISystemRootService() + { + m_Commands = new Dictionary<int, ServiceProcessRequest>() + { + { 1, GetDisplayService } + }; + } + + public long GetDisplayService(ServiceCtx Context) + { + int ServiceType = Context.RequestData.ReadInt32(); + + MakeObject(Context, new IApplicationDisplayService()); + + return 0; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs new file mode 100644 index 00000000..b45dac6b --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/NvFlinger.cs @@ -0,0 +1,452 @@ +using Ryujinx.Graphics.Gal; +using Ryujinx.HLE.Gpu; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.OsHle.Services.Nv.NvMap; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using static Ryujinx.HLE.OsHle.Services.Android.Parcel; + +namespace Ryujinx.HLE.OsHle.Services.Android +{ + class NvFlinger : IDisposable + { + private delegate long ServiceProcessParcel(ServiceCtx Context, BinaryReader ParcelReader); + + private Dictionary<(string, int), ServiceProcessParcel> Commands; + + private KEvent ReleaseEvent; + + private IGalRenderer Renderer; + + private const int BufferQueueCount = 0x40; + private const int BufferQueueMask = BufferQueueCount - 1; + + [Flags] + private enum HalTransform + { + FlipX = 1 << 0, + FlipY = 1 << 1, + Rotate90 = 1 << 2 + } + + private enum BufferState + { + Free, + Dequeued, + Queued, + Acquired + } + + private struct Rect + { + public int Top; + public int Left; + public int Right; + public int Bottom; + } + + private struct BufferEntry + { + public BufferState State; + + public HalTransform Transform; + + public Rect Crop; + + public GbpBuffer Data; + } + + private BufferEntry[] BufferQueue; + + private ManualResetEvent WaitBufferFree; + + private bool Disposed; + + public NvFlinger(IGalRenderer Renderer, KEvent ReleaseEvent) + { + Commands = new Dictionary<(string, int), ServiceProcessParcel>() + { + { ("android.gui.IGraphicBufferProducer", 0x1), GbpRequestBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x3), GbpDequeueBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x4), GbpDetachBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x7), GbpQueueBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x8), GbpCancelBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x9), GbpQuery }, + { ("android.gui.IGraphicBufferProducer", 0xa), GbpConnect }, + { ("android.gui.IGraphicBufferProducer", 0xb), GbpDisconnect }, + { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer } + }; + + this.Renderer = Renderer; + this.ReleaseEvent = ReleaseEvent; + + BufferQueue = new BufferEntry[0x40]; + + WaitBufferFree = new ManualResetEvent(false); + } + + public long ProcessParcelRequest(ServiceCtx Context, byte[] ParcelData, int Code) + { + using (MemoryStream MS = new MemoryStream(ParcelData)) + { + BinaryReader Reader = new BinaryReader(MS); + + MS.Seek(4, SeekOrigin.Current); + + int StrSize = Reader.ReadInt32(); + + string InterfaceName = Encoding.Unicode.GetString(Reader.ReadBytes(StrSize * 2)); + + long Remainder = MS.Position & 0xf; + + if (Remainder != 0) + { + MS.Seek(0x10 - Remainder, SeekOrigin.Current); + } + + MS.Seek(0x50, SeekOrigin.Begin); + + if (Commands.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq)) + { + Context.Ns.Log.PrintDebug(LogClass.ServiceVi, $"{InterfaceName} {ProcReq.Method.Name}"); + + return ProcReq(Context, Reader); + } + else + { + throw new NotImplementedException($"{InterfaceName} {Code}"); + } + } + } + + private long GbpRequestBuffer(ServiceCtx Context, BinaryReader ParcelReader) + { + int Slot = ParcelReader.ReadInt32(); + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + BufferEntry Entry = BufferQueue[Slot]; + + int BufferCount = 1; //? + long BufferSize = Entry.Data.Size; + + Writer.Write(BufferCount); + Writer.Write(BufferSize); + + Entry.Data.Write(Writer); + + Writer.Write(0); + + return MakeReplyParcel(Context, MS.ToArray()); + } + } + + private long GbpDequeueBuffer(ServiceCtx Context, BinaryReader ParcelReader) + { + //TODO: Errors. + int Format = ParcelReader.ReadInt32(); + int Width = ParcelReader.ReadInt32(); + int Height = ParcelReader.ReadInt32(); + int GetTimestamps = ParcelReader.ReadInt32(); + int Usage = ParcelReader.ReadInt32(); + + int Slot = GetFreeSlotBlocking(Width, Height); + + return MakeReplyParcel(Context, Slot, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + + private long GbpQueueBuffer(ServiceCtx Context, BinaryReader ParcelReader) + { + Context.Ns.Statistics.RecordGameFrameTime(); + + //TODO: Errors. + int Slot = ParcelReader.ReadInt32(); + int Unknown4 = ParcelReader.ReadInt32(); + int Unknown8 = ParcelReader.ReadInt32(); + int Unknownc = ParcelReader.ReadInt32(); + int Timestamp = ParcelReader.ReadInt32(); + int IsAutoTimestamp = ParcelReader.ReadInt32(); + int CropTop = ParcelReader.ReadInt32(); + int CropLeft = ParcelReader.ReadInt32(); + int CropRight = ParcelReader.ReadInt32(); + int CropBottom = ParcelReader.ReadInt32(); + int ScalingMode = ParcelReader.ReadInt32(); + int Transform = ParcelReader.ReadInt32(); + int StickyTransform = ParcelReader.ReadInt32(); + int Unknown34 = ParcelReader.ReadInt32(); + int Unknown38 = ParcelReader.ReadInt32(); + int IsFenceValid = ParcelReader.ReadInt32(); + int Fence0Id = ParcelReader.ReadInt32(); + int Fence0Value = ParcelReader.ReadInt32(); + int Fence1Id = ParcelReader.ReadInt32(); + int Fence1Value = ParcelReader.ReadInt32(); + + BufferQueue[Slot].Transform = (HalTransform)Transform; + + BufferQueue[Slot].Crop.Top = CropTop; + BufferQueue[Slot].Crop.Left = CropLeft; + BufferQueue[Slot].Crop.Right = CropRight; + BufferQueue[Slot].Crop.Bottom = CropBottom; + + BufferQueue[Slot].State = BufferState.Queued; + + SendFrameBuffer(Context, Slot); + + return MakeReplyParcel(Context, 1280, 720, 0, 0, 0); + } + + private long GbpDetachBuffer(ServiceCtx Context, BinaryReader ParcelReader) + { + return MakeReplyParcel(Context, 0); + } + + private long GbpCancelBuffer(ServiceCtx Context, BinaryReader ParcelReader) + { + //TODO: Errors. + int Slot = ParcelReader.ReadInt32(); + + BufferQueue[Slot].State = BufferState.Free; + + return MakeReplyParcel(Context, 0); + } + + private long GbpQuery(ServiceCtx Context, BinaryReader ParcelReader) + { + return MakeReplyParcel(Context, 0, 0); + } + + private long GbpConnect(ServiceCtx Context, BinaryReader ParcelReader) + { + return MakeReplyParcel(Context, 1280, 720, 0, 0, 0); + } + + private long GbpDisconnect(ServiceCtx Context, BinaryReader ParcelReader) + { + return MakeReplyParcel(Context, 0); + } + + private long GbpPreallocBuffer(ServiceCtx Context, BinaryReader ParcelReader) + { + int Slot = ParcelReader.ReadInt32(); + + int BufferCount = ParcelReader.ReadInt32(); + + if (BufferCount > 0) + { + long BufferSize = ParcelReader.ReadInt64(); + + BufferQueue[Slot].State = BufferState.Free; + + BufferQueue[Slot].Data = new GbpBuffer(ParcelReader); + } + + return MakeReplyParcel(Context, 0); + } + + private long MakeReplyParcel(ServiceCtx Context, params int[] Ints) + { + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + foreach (int Int in Ints) + { + Writer.Write(Int); + } + + return MakeReplyParcel(Context, MS.ToArray()); + } + } + + private long MakeReplyParcel(ServiceCtx Context, byte[] Data) + { + long ReplyPos = Context.Request.ReceiveBuff[0].Position; + long ReplySize = Context.Request.ReceiveBuff[0].Size; + + byte[] Reply = MakeParcel(Data, new byte[0]); + + Context.Memory.WriteBytes(ReplyPos, Reply); + + return 0; + } + + private void SendFrameBuffer(ServiceCtx Context, int Slot) + { + int FbWidth = 1280; + int FbHeight = 720; + + int NvMapHandle = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x4c); + int BufferOffset = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x50); + + NvMapHandle Map = NvMapIoctl.GetNvMap(Context, NvMapHandle);; + + long FbAddr = Map.Address + BufferOffset; + + BufferQueue[Slot].State = BufferState.Acquired; + + Rect Crop = BufferQueue[Slot].Crop; + + int RealWidth = FbWidth; + int RealHeight = FbHeight; + + float XSign = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX) ? -1 : 1; + float YSign = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY) ? -1 : 1; + + float ScaleX = 1; + float ScaleY = 1; + + float OffsX = 0; + float OffsY = 0; + + if (Crop.Right != 0 && + Crop.Bottom != 0) + { + //Who knows if this is right, I was never good with math... + RealWidth = Crop.Right - Crop.Left; + RealHeight = Crop.Bottom - Crop.Top; + + if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90)) + { + ScaleY = (float)FbHeight / RealHeight; + ScaleX = (float)FbWidth / RealWidth; + + OffsY = ((-(float)Crop.Left / Crop.Right) + ScaleX - 1) * -XSign; + OffsX = ((-(float)Crop.Top / Crop.Bottom) + ScaleY - 1) * -YSign; + } + else + { + ScaleX = (float)FbWidth / RealWidth; + ScaleY = (float)FbHeight / RealHeight; + + OffsX = ((-(float)Crop.Left / Crop.Right) + ScaleX - 1) * XSign; + OffsY = ((-(float)Crop.Top / Crop.Bottom) + ScaleY - 1) * -YSign; + } + } + + ScaleX *= XSign; + ScaleY *= YSign; + + float Rotate = 0; + + if (BufferQueue[Slot].Transform.HasFlag(HalTransform.Rotate90)) + { + Rotate = -MathF.PI * 0.5f; + } + + Renderer.SetFrameBufferTransform(ScaleX, ScaleY, Rotate, OffsX, OffsY); + + //TODO: Support double buffering here aswell, it is broken for GPU + //frame buffers because it seems to be completely out of sync. + if (Context.Ns.Gpu.Engine3d.IsFrameBufferPosition(FbAddr)) + { + //Frame buffer is rendered to by the GPU, we can just + //bind the frame buffer texture, it's not necessary to read anything. + Renderer.SetFrameBuffer(FbAddr); + } + else + { + //Frame buffer is not set on the GPU registers, in this case + //assume that the app is manually writing to it. + Texture Texture = new Texture(FbAddr, FbWidth, FbHeight); + + byte[] Data = TextureReader.Read(Context.Memory, Texture); + + Renderer.SetFrameBuffer(Data, FbWidth, FbHeight); + } + + Context.Ns.Gpu.Renderer.QueueAction(() => ReleaseBuffer(Slot)); + } + + private void ReleaseBuffer(int Slot) + { + BufferQueue[Slot].State = BufferState.Free; + + ReleaseEvent.WaitEvent.Set(); + + lock (WaitBufferFree) + { + WaitBufferFree.Set(); + } + } + + private int GetFreeSlotBlocking(int Width, int Height) + { + int Slot; + + do + { + lock (WaitBufferFree) + { + if ((Slot = GetFreeSlot(Width, Height)) != -1) + { + break; + } + + if (Disposed) + { + break; + } + + WaitBufferFree.Reset(); + } + + WaitBufferFree.WaitOne(); + } + while (!Disposed); + + return Slot; + } + + private int GetFreeSlot(int Width, int Height) + { + lock (BufferQueue) + { + for (int Slot = 0; Slot < BufferQueue.Length; Slot++) + { + if (BufferQueue[Slot].State != BufferState.Free) + { + continue; + } + + GbpBuffer Data = BufferQueue[Slot].Data; + + if (Data.Width == Width && + Data.Height == Height) + { + BufferQueue[Slot].State = BufferState.Dequeued; + + return Slot; + } + } + } + + return -1; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing && !Disposed) + { + Disposed = true; + + lock (WaitBufferFree) + { + WaitBufferFree.Set(); + } + + WaitBufferFree.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Vi/Parcel.cs b/Ryujinx.HLE/OsHle/Services/Vi/Parcel.cs new file mode 100644 index 00000000..009ed8c1 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Vi/Parcel.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; + +namespace Ryujinx.HLE.OsHle.Services.Android +{ + static class Parcel + { + public static byte[] GetParcelData(byte[] Parcel) + { + if (Parcel == null) + { + throw new ArgumentNullException(nameof(Parcel)); + } + + using (MemoryStream MS = new MemoryStream(Parcel)) + { + BinaryReader Reader = new BinaryReader(MS); + + int DataSize = Reader.ReadInt32(); + int DataOffset = Reader.ReadInt32(); + int ObjsSize = Reader.ReadInt32(); + int ObjsOffset = Reader.ReadInt32(); + + MS.Seek(DataOffset - 0x10, SeekOrigin.Current); + + return Reader.ReadBytes(DataSize); + } + } + + public static byte[] MakeParcel(byte[] Data, byte[] Objs) + { + if (Data == null) + { + throw new ArgumentNullException(nameof(Data)); + } + + if (Objs == null) + { + throw new ArgumentNullException(nameof(Objs)); + } + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write(Data.Length); + Writer.Write(0x10); + Writer.Write(Objs.Length); + Writer.Write(Data.Length + 0x10); + + Writer.Write(Data); + Writer.Write(Objs); + + return MS.ToArray(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/SystemLanguage.cs b/Ryujinx.HLE/OsHle/SystemLanguage.cs new file mode 100644 index 00000000..4f190876 --- /dev/null +++ b/Ryujinx.HLE/OsHle/SystemLanguage.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.HLE.OsHle +{ + public enum SystemLanguage + { + Japanese, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/SystemStateMgr.cs b/Ryujinx.HLE/OsHle/SystemStateMgr.cs new file mode 100644 index 00000000..e78082c4 --- /dev/null +++ b/Ryujinx.HLE/OsHle/SystemStateMgr.cs @@ -0,0 +1,84 @@ +using System; + +namespace Ryujinx.HLE.OsHle +{ + public class SystemStateMgr + { + internal static string[] LanguageCodes = new string[] + { + "ja", + "en-US", + "fr", + "de", + "it", + "es", + "zh-CN", + "ko", + "nl", + "pt", + "ru", + "zh-TW", + "en-GB", + "fr-CA", + "es-419", + "zh-Hans", + "zh-Hant" + }; + + internal static string[] AudioOutputs = new string[] + { + "AudioTvOutput", + "AudioStereoJackOutput", + "AudioBuiltInSpeakerOutput" + }; + + internal long DesiredLanguageCode { get; private set; } + + internal string ActiveAudioOutput { get; private set; } + + public SystemStateMgr() + { + SetLanguage(SystemLanguage.AmericanEnglish); + + SetAudioOutputAsBuiltInSpeaker(); + } + + public void SetLanguage(SystemLanguage Language) + { + DesiredLanguageCode = GetLanguageCode((int)Language); + } + + public void SetAudioOutputAsTv() + { + ActiveAudioOutput = AudioOutputs[0]; + } + + public void SetAudioOutputAsStereoJack() + { + ActiveAudioOutput = AudioOutputs[1]; + } + + public void SetAudioOutputAsBuiltInSpeaker() + { + ActiveAudioOutput = AudioOutputs[2]; + } + + internal static long GetLanguageCode(int Index) + { + if ((uint)Index >= LanguageCodes.Length) + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + + long Code = 0; + int Shift = 0; + + foreach (char Chr in LanguageCodes[Index]) + { + Code |= (long)(byte)Chr << Shift++ * 8; + } + + return Code; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Utilities/EndianSwap.cs b/Ryujinx.HLE/OsHle/Utilities/EndianSwap.cs new file mode 100644 index 00000000..93fd38c8 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Utilities/EndianSwap.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.OsHle.Utilities +{ + static class EndianSwap + { + public static short Swap16(short Value) => (short)(((Value >> 8) & 0xff) | (Value << 8)); + } +} diff --git a/Ryujinx.HLE/OsHle/Utilities/IntUtils.cs b/Ryujinx.HLE/OsHle/Utilities/IntUtils.cs new file mode 100644 index 00000000..ba0726c3 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Utilities/IntUtils.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.OsHle.Utilities +{ + static class IntUtils + { + public static int RoundUp(int Value, int Size) + { + return (Value + (Size - 1)) & ~(Size - 1); + } + + public static long RoundUp(long Value, int Size) + { + return (Value + (Size - 1)) & ~((long)Size - 1); + } + } +} diff --git a/Ryujinx.HLE/PerformanceStatistics.cs b/Ryujinx.HLE/PerformanceStatistics.cs new file mode 100644 index 00000000..bbcdc645 --- /dev/null +++ b/Ryujinx.HLE/PerformanceStatistics.cs @@ -0,0 +1,84 @@ +using System.Diagnostics; +using System.Timers; + +namespace Ryujinx.HLE +{ + public class PerformanceStatistics + { + Stopwatch ExecutionTime = new Stopwatch(); + Timer ResetTimer = new Timer(1000); + + long CurrentGameFrameEnded; + long CurrentSystemFrameEnded; + long CurrentSystemFrameStart; + long LastGameFrameEnded; + long LastSystemFrameEnded; + + double AccumulatedGameFrameTime; + double AccumulatedSystemFrameTime; + double CurrentGameFrameTime; + double CurrentSystemFrameTime; + double PreviousGameFrameTime; + double PreviousSystemFrameTime; + public double GameFrameRate { get; private set; } + public double SystemFrameRate { get; private set; } + public long SystemFramesRendered; + public long GameFramesRendered; + public long ElapsedMilliseconds => ExecutionTime.ElapsedMilliseconds; + public long ElapsedMicroseconds => (long) + (((double)ExecutionTime.ElapsedTicks / Stopwatch.Frequency) * 1000000); + public long ElapsedNanoseconds => (long) + (((double)ExecutionTime.ElapsedTicks / Stopwatch.Frequency) * 1000000000); + + public PerformanceStatistics() + { + ExecutionTime.Start(); + ResetTimer.Elapsed += ResetTimerElapsed; + ResetTimer.AutoReset = true; + ResetTimer.Start(); + } + + private void ResetTimerElapsed(object sender, ElapsedEventArgs e) + { + ResetStatistics(); + } + + public void StartSystemFrame() + { + PreviousSystemFrameTime = CurrentSystemFrameTime; + LastSystemFrameEnded = CurrentSystemFrameEnded; + CurrentSystemFrameStart = ElapsedMicroseconds; + } + + public void EndSystemFrame() + { + CurrentSystemFrameEnded = ElapsedMicroseconds; + CurrentSystemFrameTime = CurrentSystemFrameEnded - CurrentSystemFrameStart; + AccumulatedSystemFrameTime += CurrentSystemFrameTime; + SystemFramesRendered++; + } + + public void RecordGameFrameTime() + { + CurrentGameFrameEnded = ElapsedMicroseconds; + CurrentGameFrameTime = CurrentGameFrameEnded - LastGameFrameEnded; + PreviousGameFrameTime = CurrentGameFrameTime; + LastGameFrameEnded = CurrentGameFrameEnded; + AccumulatedGameFrameTime += CurrentGameFrameTime; + GameFramesRendered++; + } + + public void ResetStatistics() + { + GameFrameRate = 1000 / ((AccumulatedGameFrameTime / GameFramesRendered) / 1000); + GameFrameRate = double.IsNaN(GameFrameRate) ? 0 : GameFrameRate; + SystemFrameRate = 1000 / ((AccumulatedSystemFrameTime / SystemFramesRendered) / 1000); + SystemFrameRate = double.IsNaN(SystemFrameRate) ? 0 : SystemFrameRate; + + GameFramesRendered = 0; + SystemFramesRendered = 0; + AccumulatedGameFrameTime = 0; + AccumulatedSystemFrameTime = 0; + } + } +} diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj new file mode 100644 index 00000000..27e43626 --- /dev/null +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -0,0 +1,21 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netcoreapp2.1</TargetFramework> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\ChocolArm64\ChocolArm64.csproj" /> + <ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" /> + <ProjectReference Include="..\Ryujinx.Graphics\Ryujinx.Graphics.csproj" /> + </ItemGroup> + +</Project> diff --git a/Ryujinx.HLE/Settings/ColorSet.cs b/Ryujinx.HLE/Settings/ColorSet.cs new file mode 100644 index 00000000..77485d22 --- /dev/null +++ b/Ryujinx.HLE/Settings/ColorSet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.Settings +{ + public enum ColorSet + { + BasicWhite = 0, + BasicBlack = 1 + } +} diff --git a/Ryujinx.HLE/Settings/SystemSettings.cs b/Ryujinx.HLE/Settings/SystemSettings.cs new file mode 100644 index 00000000..555e83bb --- /dev/null +++ b/Ryujinx.HLE/Settings/SystemSettings.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.Settings +{ + public class SystemSettings + { + public ColorSet ThemeColor; + } +} diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs new file mode 100644 index 00000000..f75b2490 --- /dev/null +++ b/Ryujinx.HLE/Switch.cs @@ -0,0 +1,93 @@ +using Ryujinx.Audio; +using Ryujinx.Graphics.Gal; +using Ryujinx.HLE.Gpu; +using Ryujinx.HLE.Input; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle; +using Ryujinx.HLE.Settings; +using System; + +namespace Ryujinx.HLE +{ + public class Switch : IDisposable + { + internal IAalOutput AudioOut { get; private set; } + + public Logger Log { get; private set; } + + internal NvGpu Gpu { get; private set; } + + internal VirtualFileSystem VFs { get; private set; } + + public Horizon Os { get; private set; } + + public SystemSettings Settings { get; private set; } + + public PerformanceStatistics Statistics { get; private set; } + + public Hid Hid { get; private set; } + + public event EventHandler Finish; + + public Switch(IGalRenderer Renderer, IAalOutput AudioOut) + { + if (Renderer == null) + { + throw new ArgumentNullException(nameof(Renderer)); + } + + if (AudioOut == null) + { + throw new ArgumentNullException(nameof(AudioOut)); + } + + this.AudioOut = AudioOut; + + Log = new Logger(); + + Gpu = new NvGpu(Renderer); + + VFs = new VirtualFileSystem(); + + Os = new Horizon(this); + + Settings = new SystemSettings(); + + Statistics = new PerformanceStatistics(); + + Hid = new Hid(Log); + + Os.HidSharedMem.MemoryMapped += Hid.ShMemMap; + Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap; + } + + public void LoadCart(string ExeFsDir, string RomFsFile = null) + { + Os.LoadCart(ExeFsDir, RomFsFile); + } + + public void LoadProgram(string FileName) + { + Os.LoadProgram(FileName); + } + + internal virtual void OnFinish(EventArgs e) + { + Finish?.Invoke(this, e); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + Os.Dispose(); + VFs.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/VirtualFileSystem.cs b/Ryujinx.HLE/VirtualFileSystem.cs new file mode 100644 index 00000000..8b71caa9 --- /dev/null +++ b/Ryujinx.HLE/VirtualFileSystem.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; + +namespace Ryujinx.HLE +{ + class VirtualFileSystem : IDisposable + { + private const string BasePath = "RyuFs"; + private const string NandPath = "nand"; + private const string SdCardPath = "sdmc"; + + public Stream RomFs { get; private set; } + + public void LoadRomFs(string FileName) + { + RomFs = new FileStream(FileName, FileMode.Open, FileAccess.Read); + } + + public string GetFullPath(string BasePath, string FileName) + { + if (FileName.StartsWith("//")) + { + FileName = FileName.Substring(2); + } + else if (FileName.StartsWith('/')) + { + FileName = FileName.Substring(1); + } + else + { + return null; + } + + string FullPath = Path.GetFullPath(Path.Combine(BasePath, FileName)); + + if (!FullPath.StartsWith(GetBasePath())) + { + return null; + } + + return FullPath; + } + + public string GetSdCardPath() => MakeDirAndGetFullPath(SdCardPath); + + public string GetGameSavesPath() => MakeDirAndGetFullPath(NandPath); + + private string MakeDirAndGetFullPath(string Dir) + { + string FullPath = Path.Combine(GetBasePath(), Dir); + + if (!Directory.Exists(FullPath)) + { + Directory.CreateDirectory(FullPath); + } + + return FullPath; + } + + public DriveInfo GetDrive() + { + return new DriveInfo(Path.GetPathRoot(GetBasePath())); + } + + public string GetBasePath() + { + string AppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + + return Path.Combine(AppDataPath, BasePath); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + RomFs?.Dispose(); + } + } + } +}
\ No newline at end of file |
