diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..cca6c60806 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: "Build job" +on: + push: + branches: + - master + pull_request: + branches: + - '*' +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest, windows-latest] + dotnet: ['3.1.100'] + environment: ['Debug', 'Release', 'Profile Debug', 'Profile Release'] + name: ${{ matrix.environment }} build (Dotnet ${{ matrix.dotnet }}, OS ${{ matrix.os }}) + steps: + - uses: actions/checkout@master + - name: Setup dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{ matrix.dotnet }} + - name: Build + run: dotnet build -c "${{ matrix.environment }}" + - name: Test + run: dotnet test -c "${{ matrix.environment }}" \ No newline at end of file diff --git a/ARMeilleure/CodeGen/X86/Assembler.cs b/ARMeilleure/CodeGen/X86/Assembler.cs index 955bc6fa3f..94d4554835 100644 --- a/ARMeilleure/CodeGen/X86/Assembler.cs +++ b/ARMeilleure/CodeGen/X86/Assembler.cs @@ -106,6 +106,7 @@ namespace ARMeilleure.CodeGen.X86 Add(X86Instruction.Cvtsi2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF2)); Add(X86Instruction.Cvtsi2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF3)); Add(X86Instruction.Cvtss2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cvtss2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF3)); Add(X86Instruction.Div, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x060000f7, InstructionFlags.None)); Add(X86Instruction.Divpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Divps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex)); @@ -784,6 +785,16 @@ namespace ARMeilleure.CodeGen.X86 } } + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + OperandType type) + { + WriteInstruction(dest, src1, src2, inst, type); + } + public void WriteInstruction(X86Instruction inst, Operand dest, Operand source, byte imm) { WriteInstruction(dest, null, source, inst); diff --git a/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/ARMeilleure/CodeGen/X86/CodeGenerator.cs index 41e953dd63..e8b28df280 100644 --- a/ARMeilleure/CodeGen/X86/CodeGenerator.cs +++ b/ARMeilleure/CodeGen/X86/CodeGenerator.cs @@ -269,11 +269,11 @@ namespace ARMeilleure.CodeGen.X86 { if (dest.Type == OperandType.I32) { - context.Assembler.Movd(dest, source); // int _mm_cvtsi128_si32 + context.Assembler.Movd(dest, source); // int _mm_cvtsi128_si32(__m128i a) } else /* if (dest.Type == OperandType.I64) */ { - context.Assembler.Movq(dest, source); // __int64 _mm_cvtsi128_si64 + context.Assembler.Movq(dest, source); // __int64 _mm_cvtsi128_si64(__m128i a) } } else @@ -305,6 +305,26 @@ namespace ARMeilleure.CodeGen.X86 break; } + case IntrinsicType.BinaryGpr: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src2.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src2.Type); + + break; + } + case IntrinsicType.BinaryImm: { Operand dest = operation.Destination; @@ -1070,11 +1090,11 @@ namespace ARMeilleure.CodeGen.X86 if (source.Type == OperandType.I32) { - context.Assembler.Movd(dest, source); + context.Assembler.Movd(dest, source); // (__m128i _mm_cvtsi32_si128(int a)) } else /* if (source.Type == OperandType.I64) */ { - context.Assembler.Movq(dest, source); + context.Assembler.Movq(dest, source); // (__m128i _mm_cvtsi64_si128(__int64 a)) } } diff --git a/ARMeilleure/CodeGen/X86/IntrinsicTable.cs b/ARMeilleure/CodeGen/X86/IntrinsicTable.cs index e87de03509..fd3b691d4f 100644 --- a/ARMeilleure/CodeGen/X86/IntrinsicTable.cs +++ b/ARMeilleure/CodeGen/X86/IntrinsicTable.cs @@ -41,8 +41,11 @@ namespace ARMeilleure.CodeGen.X86 Add(Intrinsic.X86Cvtps2pd, new IntrinsicInfo(X86Instruction.Cvtps2pd, IntrinsicType.Unary)); Add(Intrinsic.X86Cvtsd2si, new IntrinsicInfo(X86Instruction.Cvtsd2si, IntrinsicType.UnaryToGpr)); Add(Intrinsic.X86Cvtsd2ss, new IntrinsicInfo(X86Instruction.Cvtsd2ss, IntrinsicType.Binary)); + Add(Intrinsic.X86Cvtsi2sd, new IntrinsicInfo(X86Instruction.Cvtsi2sd, IntrinsicType.BinaryGpr)); Add(Intrinsic.X86Cvtsi2si, new IntrinsicInfo(X86Instruction.Movd, IntrinsicType.UnaryToGpr)); + Add(Intrinsic.X86Cvtsi2ss, new IntrinsicInfo(X86Instruction.Cvtsi2ss, IntrinsicType.BinaryGpr)); Add(Intrinsic.X86Cvtss2sd, new IntrinsicInfo(X86Instruction.Cvtss2sd, IntrinsicType.Binary)); + Add(Intrinsic.X86Cvtss2si, new IntrinsicInfo(X86Instruction.Cvtss2si, IntrinsicType.UnaryToGpr)); Add(Intrinsic.X86Divpd, new IntrinsicInfo(X86Instruction.Divpd, IntrinsicType.Binary)); Add(Intrinsic.X86Divps, new IntrinsicInfo(X86Instruction.Divps, IntrinsicType.Binary)); Add(Intrinsic.X86Divsd, new IntrinsicInfo(X86Instruction.Divsd, IntrinsicType.Binary)); diff --git a/ARMeilleure/CodeGen/X86/IntrinsicType.cs b/ARMeilleure/CodeGen/X86/IntrinsicType.cs index 4e9b33e1ed..41c52b59db 100644 --- a/ARMeilleure/CodeGen/X86/IntrinsicType.cs +++ b/ARMeilleure/CodeGen/X86/IntrinsicType.cs @@ -7,6 +7,7 @@ namespace ARMeilleure.CodeGen.X86 Unary, UnaryToGpr, Binary, + BinaryGpr, BinaryImm, Ternary, TernaryImm diff --git a/ARMeilleure/CodeGen/X86/X86Instruction.cs b/ARMeilleure/CodeGen/X86/X86Instruction.cs index a29e68fb6f..813730f2a3 100644 --- a/ARMeilleure/CodeGen/X86/X86Instruction.cs +++ b/ARMeilleure/CodeGen/X86/X86Instruction.cs @@ -38,6 +38,7 @@ namespace ARMeilleure.CodeGen.X86 Cvtsi2sd, Cvtsi2ss, Cvtss2sd, + Cvtss2si, Div, Divpd, Divps, diff --git a/ARMeilleure/Decoders/Decoder.cs b/ARMeilleure/Decoders/Decoder.cs index 2311e9e96e..8eb2a99d6d 100644 --- a/ARMeilleure/Decoders/Decoder.cs +++ b/ARMeilleure/Decoders/Decoder.cs @@ -10,6 +10,11 @@ namespace ARMeilleure.Decoders { static class Decoder { + // We define a limit on the number of instructions that a function may have, + // this prevents functions being potentially too large, which would + // take too long to compile and use too much memory. + private const int MaxInstsPerFunction = 5000; + private delegate object MakeOp(InstDescriptor inst, ulong address, int opCode); private static ConcurrentDictionary _opActivators; @@ -36,10 +41,17 @@ namespace ARMeilleure.Decoders Dictionary visited = new Dictionary(); + int opsCount = 0; + Block GetBlock(ulong blkAddress) { if (!visited.TryGetValue(blkAddress, out Block block)) { + if (opsCount > MaxInstsPerFunction) + { + return null; + } + block = new Block(blkAddress); workQueue.Enqueue(block); @@ -92,6 +104,8 @@ namespace ARMeilleure.Decoders FillBlock(memory, mode, currBlock, limitAddress); + opsCount += currBlock.OpCodes.Count; + if (currBlock.OpCodes.Count != 0) { // Set child blocks. "Branch" is the block the branch instruction diff --git a/ARMeilleure/Instructions/InstEmitAlu.cs b/ARMeilleure/Instructions/InstEmitAlu.cs index 3d3e82a2fc..ed1faae417 100644 --- a/ARMeilleure/Instructions/InstEmitAlu.cs +++ b/ARMeilleure/Instructions/InstEmitAlu.cs @@ -2,6 +2,7 @@ using ARMeilleure.Decoders; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.State; using ARMeilleure.Translation; +using System.Diagnostics; using static ARMeilleure.Instructions.InstEmitAluHelper; using static ARMeilleure.Instructions.InstEmitHelper; @@ -265,16 +266,52 @@ namespace ARMeilleure.Instructions if (op.RegisterSize == RegisterSize.Int32) { - d = context.SoftFallbackCall(nameof(SoftFallback.ReverseBits32), n); + d = EmitReverseBits32Op(context, n); } else { - d = context.SoftFallbackCall(nameof(SoftFallback.ReverseBits64), n); + d = EmitReverseBits64Op(context, n); } SetAluDOrZR(context, d); } + private static Operand EmitReverseBits32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaau)), Const(1)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x55555555u)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccu)), Const(2)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x33333333u)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0u)), Const(4)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0f0f0f0fu)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00u)), Const(8)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x00ff00ffu)), Const(8))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(16)), context.ShiftLeft(val, Const(16))); + } + + private static Operand EmitReverseBits64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaaaaaaaaaaul)), Const(1)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x5555555555555555ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccccccccccul)), Const(2)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x3333333333333333ul)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0f0f0f0f0ul)), Const(4)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0f0f0f0f0f0f0f0ful)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x00ff00ff00ff00fful)), Const(8))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(32)), context.ShiftLeft(val, Const(32))); + } + public static void Rev16(ArmEmitterContext context) { OpCodeAlu op = (OpCodeAlu)context.CurrOp; @@ -284,32 +321,60 @@ namespace ARMeilleure.Instructions if (op.RegisterSize == RegisterSize.Int32) { - d = context.SoftFallbackCall(nameof(SoftFallback.ReverseBytes16_32), n); + d = EmitReverseBytes16_32Op(context, n); } else { - d = context.SoftFallbackCall(nameof(SoftFallback.ReverseBytes16_64), n); + d = EmitReverseBytes16_64Op(context, n); } SetAluDOrZR(context, d); } + private static Operand EmitReverseBytes16_32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = EmitReverseBytes16_64Op(context, context.ZeroExtend32(OperandType.I64, op)); + + return context.ConvertI64ToI32(val); + } + + private static Operand EmitReverseBytes16_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x00ff00ff00ff00fful)), Const(8))); + } + public static void Rev32(ArmEmitterContext context) { OpCodeAlu op = (OpCodeAlu)context.CurrOp; Operand n = GetIntOrZR(context, op.Rn); + Operand d; if (op.RegisterSize == RegisterSize.Int32) { - SetAluDOrZR(context, context.ByteSwap(n)); + d = context.ByteSwap(n); } else { - Operand d = context.SoftFallbackCall(nameof(SoftFallback.ReverseBytes32_64), n); - - SetAluDOrZR(context, d); + d = EmitReverseBytes32_64Op(context, n); } + + SetAluDOrZR(context, d); + } + + private static Operand EmitReverseBytes32_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = EmitReverseBytes16_64Op(context, op); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); } public static void Rev64(ArmEmitterContext context) diff --git a/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs index e89f0bd140..f9e014a3d2 100644 --- a/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs +++ b/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -77,7 +77,14 @@ namespace ARMeilleure.Instructions public static void Addp_V(ArmEmitterContext context) { - EmitVectorPairwiseOpZx(context, (op1, op2) => context.Add(op1, op2)); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Add(op1, op2)); + } } public static void Addv_V(ArmEmitterContext context) @@ -390,7 +397,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitVectorPairwiseOpF(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); } else { @@ -538,7 +545,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitVectorPairwiseOpF(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); } else { @@ -613,7 +620,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitVectorPairwiseOpF(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); + EmitSse2VectorPairwiseOpF(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); } else { @@ -655,7 +662,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else /* if (sizeF == 1) */ { @@ -663,7 +670,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } else @@ -701,7 +708,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else /* if (sizeF == 1) */ { @@ -712,7 +719,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } else @@ -755,7 +762,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else /* if (sizeF == 1) */ { @@ -763,7 +770,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } else @@ -801,7 +808,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else /* if (sizeF == 1) */ { @@ -812,7 +819,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } else @@ -2019,9 +2026,16 @@ namespace ARMeilleure.Instructions public static void Smaxp_V(ArmEmitterContext context) { - string name = nameof(SoftFallback.MaxS64); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxsInstruction); + } + else + { + string name = nameof(SoftFallback.MaxS64); - EmitVectorPairwiseOpSx(context, (op1, op2) => context.SoftFallbackCall(name, op1, op2)); + EmitVectorPairwiseOpSx(context, (op1, op2) => context.SoftFallbackCall(name, op1, op2)); + } } public static void Smaxv_V(ArmEmitterContext context) @@ -2061,9 +2075,16 @@ namespace ARMeilleure.Instructions public static void Sminp_V(ArmEmitterContext context) { - string name = nameof(SoftFallback.MinS64); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminsInstruction); + } + else + { + string name = nameof(SoftFallback.MinS64); - EmitVectorPairwiseOpSx(context, (op1, op2) => context.SoftFallbackCall(name, op1, op2)); + EmitVectorPairwiseOpSx(context, (op1, op2) => context.SoftFallbackCall(name, op1, op2)); + } } public static void Sminv_V(ArmEmitterContext context) @@ -2644,9 +2665,16 @@ namespace ARMeilleure.Instructions public static void Umaxp_V(ArmEmitterContext context) { - string name = nameof(SoftFallback.MaxU64); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxuInstruction); + } + else + { + string name = nameof(SoftFallback.MaxU64); - EmitVectorPairwiseOpZx(context, (op1, op2) => context.SoftFallbackCall(name, op1, op2)); + EmitVectorPairwiseOpZx(context, (op1, op2) => context.SoftFallbackCall(name, op1, op2)); + } } public static void Umaxv_V(ArmEmitterContext context) @@ -2686,9 +2714,16 @@ namespace ARMeilleure.Instructions public static void Uminp_V(ArmEmitterContext context) { - string name = nameof(SoftFallback.MinU64); + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminuInstruction); + } + else + { + string name = nameof(SoftFallback.MinU64); - EmitVectorPairwiseOpZx(context, (op1, op2) => context.SoftFallbackCall(name, op1, op2)); + EmitVectorPairwiseOpZx(context, (op1, op2) => context.SoftFallbackCall(name, op1, op2)); + } } public static void Uminv_V(ArmEmitterContext context) @@ -3011,7 +3046,9 @@ namespace ARMeilleure.Instructions int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); long roundConst = 1L << (eSize - 1); @@ -3032,7 +3069,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, de, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } public static void EmitScalarRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) @@ -3115,12 +3152,12 @@ namespace ARMeilleure.Instructions Operand n = GetVec(op.Rn); Operand m = GetVec(op.Rm); - Operand nQNaNMask = EmitSse2VectorIsQNaNOpF(context, n); - Operand mQNaNMask = EmitSse2VectorIsQNaNOpF(context, m); - Operand nNum = context.Copy(n); Operand mNum = context.Copy(m); + Operand nQNaNMask = EmitSse2VectorIsQNaNOpF(context, nNum); + Operand mQNaNMask = EmitSse2VectorIsQNaNOpF(context, mNum); + int sizeF = op.Size & 1; if (sizeF == 0) diff --git a/ARMeilleure/Instructions/InstEmitSimdCvt.cs b/ARMeilleure/Instructions/InstEmitSimdCvt.cs index 462f79311e..0069a5ddf1 100644 --- a/ARMeilleure/Instructions/InstEmitSimdCvt.cs +++ b/ARMeilleure/Instructions/InstEmitSimdCvt.cs @@ -157,12 +157,26 @@ namespace ARMeilleure.Instructions public static void Fcvtms_Gp(ArmEmitterContext context) { - EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + } } public static void Fcvtmu_Gp(ArmEmitterContext context) { - EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + } } public static void Fcvtn_V(ArmEmitterContext context) @@ -174,11 +188,10 @@ namespace ARMeilleure.Instructions if (Optimizations.UseSse2 && sizeF == 1) { Operand d = GetVec(op.Rd); - Operand n = GetVec(op.Rn); - Operand res = context.AddIntrinsic(Intrinsic.X86Movlhps, d, context.VectorZero()); + Operand res = context.VectorZeroUpper64(d); - Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtpd2ps, n); + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtpd2ps, GetVec(op.Rn)); nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); @@ -188,7 +201,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(movInst, res, nInt); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else { @@ -198,7 +211,9 @@ namespace ARMeilleure.Instructions int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); for (int index = 0; index < elems; index++) { @@ -220,7 +235,7 @@ namespace ARMeilleure.Instructions } } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } @@ -274,22 +289,50 @@ namespace ARMeilleure.Instructions public static void Fcvtps_Gp(ArmEmitterContext context) { - EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + } } public static void Fcvtpu_Gp(ArmEmitterContext context) { - EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + } } public static void Fcvtzs_Gp(ArmEmitterContext context) { - EmitFcvt_s_Gp(context, (op1) => op1); + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => op1); + } } public static void Fcvtzs_Gp_Fixed(ArmEmitterContext context) { - EmitFcvtzs_Gp_Fixed(context); + if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzs_Gp_Fixed(context); + } } public static void Fcvtzs_S(ArmEmitterContext context) @@ -330,12 +373,26 @@ namespace ARMeilleure.Instructions public static void Fcvtzu_Gp(ArmEmitterContext context) { - EmitFcvt_u_Gp(context, (op1) => op1); + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => op1); + } } public static void Fcvtzu_Gp_Fixed(ArmEmitterContext context) { - EmitFcvtzu_Gp_Fixed(context); + if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzu_Gp_Fixed(context); + } } public static void Fcvtzu_S(ArmEmitterContext context) @@ -410,16 +467,16 @@ namespace ARMeilleure.Instructions public static void Scvtf_S(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Scvtf(context, scalar: true); } else { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + Operand res = EmitVectorLongExtract(context, op.Rn, 0, sizeF + 2); res = EmitFPConvert(context, res, op.Size, signed: true); @@ -430,11 +487,7 @@ namespace ARMeilleure.Instructions public static void Scvtf_V(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Scvtf(context, scalar: false); } @@ -446,12 +499,7 @@ namespace ARMeilleure.Instructions public static void Scvtf_V_Fixed(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - // sizeF == ((OpCodeSimdShImm64)op).Size - 2 - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Scvtf(context, scalar: false); } @@ -487,16 +535,16 @@ namespace ARMeilleure.Instructions public static void Ucvtf_S(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Ucvtf(context, scalar: true); } else { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + Operand ne = EmitVectorLongExtract(context, op.Rn, 0, sizeF + 2); Operand res = EmitFPConvert(context, ne, sizeF, signed: false); @@ -507,11 +555,7 @@ namespace ARMeilleure.Instructions public static void Ucvtf_V(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Ucvtf(context, scalar: false); } @@ -523,12 +567,7 @@ namespace ARMeilleure.Instructions public static void Ucvtf_V_Fixed(ArmEmitterContext context) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; - - // sizeF == ((OpCodeSimdShImm)op).Size - 2 - int sizeF = op.Size & 1; - - if (Optimizations.UseSse2 && sizeF == 0) + if (Optimizations.UseSse2) { EmitSse2Ucvtf(context, scalar: false); } @@ -806,43 +845,69 @@ namespace ARMeilleure.Instructions } } - private static void EmitSse41Fcvts(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + private static Operand EmitSse2CvtDoubleToInt64OpF(ArmEmitterContext context, Operand opF, bool scalar) + { + Debug.Assert(opF.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong (Intrinsic.X86Cvtsd2si, opF); // opFL + Operand res = context.VectorCreateScalar(longL); + + if (!scalar) + { + Operand opFH = context.AddIntrinsic (Intrinsic.X86Movhlps, res, opF); // res doesn't matter. + Operand longH = context.AddIntrinsicLong (Intrinsic.X86Cvtsd2si, opFH); + Operand resH = context.VectorCreateScalar(longH); + res = context.AddIntrinsic (Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static Operand EmitSse2CvtInt64ToDoubleOp(ArmEmitterContext context, Operand op, bool scalar) + { + Debug.Assert(op.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, op); // opL + Operand res = context.AddIntrinsic (Intrinsic.X86Cvtsi2sd, context.VectorZero(), longL); + + if (!scalar) + { + Operand opH = context.AddIntrinsic (Intrinsic.X86Movhlps, res, op); // res doesn't matter. + Operand longH = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, opH); + Operand resH = context.AddIntrinsic (Intrinsic.X86Cvtsi2sd, res, longH); // res doesn't matter. + res = context.AddIntrinsic (Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static void EmitSse2Scvtf(ArmEmitterContext context, bool scalar) { OpCodeSimd op = (OpCodeSimd)context.CurrOp; Operand n = GetVec(op.Rn); - // sizeF == ((OpCodeSimdShImm64)op).Size - 2 + // sizeF == ((OpCodeSimdShImm)op).Size - 2 int sizeF = op.Size & 1; if (sizeF == 0) { - Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); - - Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n); + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); if (op is OpCodeSimdShImm fixedOp) { int fBits = GetImmShr(fixedOp); - // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) - int fpScaled = 0x3F800000 + fBits * 0x800000; + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); - nScaled = context.AddIntrinsic(Intrinsic.X86Mulps, nScaled, scale); + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); } - Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundps, nScaled, Const(X86GetRoundControl(roundMode))); - - Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRnd); - - Operand mask = X86GetAllElements(context, 0x4F000000); // 2.14748365E9f (2147483648) - - Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, nRnd, mask, Const((int)CmpCondition.NotLessThan)); - - Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, mask2); - if (scalar) { res = context.VectorZeroUpper96(res); @@ -856,9 +921,175 @@ namespace ARMeilleure.Instructions } else /* if (sizeF == 1) */ { - Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + Operand res = EmitSse2CvtInt64ToDoubleOp(context, n, scalar); - Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n); + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse2Ucvtf(ArmEmitterContext context, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = scalar // 65536.000f (1 << 16) + ? X86GetScalar (context, 0x47800000) + : X86GetAllElements(context, 0x47800000); + + Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16)); + res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res); + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2); + + res = context.AddIntrinsic(Intrinsic.X86Addps, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = scalar // 4294967296.0000000d (1L << 32) + ? X86GetScalar (context, 0x41F0000000000000L) + : X86GetAllElements(context, 0x41F0000000000000L); + + Operand res = context.AddIntrinsic (Intrinsic.X86Psrlq, n, Const(32)); + res = EmitSse2CvtInt64ToDoubleOp(context, res, scalar); + res = context.AddIntrinsic (Intrinsic.X86Mulpd, res, mask); + + Operand res2 = context.AddIntrinsic (Intrinsic.X86Psllq, n, Const(32)); + res2 = context.AddIntrinsic (Intrinsic.X86Psrlq, res2, Const(32)); + res2 = EmitSse2CvtInt64ToDoubleOp(context, res2, scalar); + + res = context.AddIntrinsic(Intrinsic.X86Addpd, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse41Fcvts(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 + fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar (context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, nRes); + + if (scalar) + { + dRes = context.VectorZeroUpper96(dRes); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); if (op is OpCodeSimdShImm fixedOp) { @@ -867,41 +1098,31 @@ namespace ARMeilleure.Instructions // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); - nScaled = context.AddIntrinsic(Intrinsic.X86Mulpd, nScaled, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); } - Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundpd, nScaled, Const(X86GetRoundControl(roundMode))); + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); - Operand high; + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); - if (!scalar) - { - high = context.AddIntrinsic(Intrinsic.X86Unpckhpd, nRnd, nRnd); - high = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, high); - } - else - { - high = Const(0L); - } + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar (context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); - Operand low = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRnd); + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); - Operand nInt = EmitVectorLongCreate(context, low, high); - - Operand mask = X86GetAllElements(context, 0x43E0000000000000L); // 9.2233720368547760E18d (9223372036854775808) - - Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, nRnd, mask, Const((int)CmpCondition.NotLessThan)); - - Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, mask2); + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong, nRes); if (scalar) { - res = context.VectorZeroUpper64(res); + dRes = context.VectorZeroUpper64(dRes); } - context.Copy(GetVec(op.Rd), res); + context.Copy(GetVec(op.Rd), dRes); } } @@ -916,9 +1137,8 @@ namespace ARMeilleure.Instructions if (sizeF == 0) { - Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); - - Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n); + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); if (op is OpCodeSimdShImm fixedOp) { @@ -927,50 +1147,53 @@ namespace ARMeilleure.Instructions // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) int fpScaled = 0x3F800000 + fBits * 0x800000; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); - nScaled = context.AddIntrinsic(Intrinsic.X86Mulps, nScaled, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); } - Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundps, nScaled, Const(X86GetRoundControl(roundMode))); + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); - Operand nRndMask = context.AddIntrinsic(Intrinsic.X86Cmpps, nRnd, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual)); + Operand zero = context.VectorZero(); - Operand nRndMasked = context.AddIntrinsic(Intrinsic.X86Pand, nRnd, nRndMask); + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); - Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRndMasked); + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar (context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); - Operand mask = X86GetAllElements(context, 0x4F000000); // 2.14748365E9f (2147483648) + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); - Operand res = context.AddIntrinsic(Intrinsic.X86Subps, nRndMasked, mask); + nRes = context.AddIntrinsic(Intrinsic.X86Subps, nRes, fpMaxValMask); - Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, res, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual)); + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); - Operand resMasked = context.AddIntrinsic(Intrinsic.X86Pand, res, mask2); + Operand nInt2 = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); - res = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, resMasked); + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); - Operand mask3 = context.AddIntrinsic(Intrinsic.X86Cmpps, resMasked, mask, Const((int)CmpCondition.NotLessThan)); - - res = context.AddIntrinsic(Intrinsic.X86Pxor, res, mask3); - res = context.AddIntrinsic(Intrinsic.X86Paddd, res, nInt); + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddd, dRes, nInt); if (scalar) { - res = context.VectorZeroUpper96(res); + dRes = context.VectorZeroUpper96(dRes); } else if (op.RegisterSize == RegisterSize.Simd64) { - res = context.VectorZeroUpper64(res); + dRes = context.VectorZeroUpper64(dRes); } - context.Copy(GetVec(op.Rd), res); + context.Copy(GetVec(op.Rd), dRes); } else /* if (sizeF == 1) */ { - Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); - - Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n); + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); if (op is OpCodeSimdShImm fixedOp) { @@ -979,140 +1202,251 @@ namespace ARMeilleure.Instructions // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); - nScaled = context.AddIntrinsic(Intrinsic.X86Mulpd, nScaled, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); } - Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundpd, nScaled, Const(X86GetRoundControl(roundMode))); + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); - Operand nRndMask = context.AddIntrinsic(Intrinsic.X86Cmppd, nRnd, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual)); + Operand zero = context.VectorZero(); - Operand nRndMasked = context.AddIntrinsic(Intrinsic.X86Pand, nRnd, nRndMask); + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); - Operand high; + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar (context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); - if (!scalar) - { - high = context.AddIntrinsic(Intrinsic.X86Unpckhpd, nRndMasked, nRndMasked); - high = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, high); - } - else - { - high = Const(0L); - } + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); - Operand low = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRndMasked); + nRes = context.AddIntrinsic(Intrinsic.X86Subpd, nRes, fpMaxValMask); - Operand nInt = EmitVectorLongCreate(context, low, high); + nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); - Operand mask = X86GetAllElements(context, 0x43E0000000000000L); // 9.2233720368547760E18d (9223372036854775808) + Operand nLong2 = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); - Operand res = context.AddIntrinsic(Intrinsic.X86Subpd, nRndMasked, mask); + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); - Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, res, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual)); - - Operand resMasked = context.AddIntrinsic(Intrinsic.X86Pand, res, mask2); - - if (!scalar) - { - high = context.AddIntrinsic(Intrinsic.X86Unpckhpd, resMasked, resMasked); - high = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, high); - } - - low = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, resMasked); - - res = EmitVectorLongCreate(context, low, high); - - Operand mask3 = context.AddIntrinsic(Intrinsic.X86Cmppd, resMasked, mask, Const((int)CmpCondition.NotLessThan)); - - res = context.AddIntrinsic(Intrinsic.X86Pxor, res, mask3); - res = context.AddIntrinsic(Intrinsic.X86Paddq, res, nInt); + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddq, dRes, nLong); if (scalar) { - res = context.VectorZeroUpper64(res); + dRes = context.VectorZeroUpper64(dRes); } - context.Copy(GetVec(op.Rd), res); + context.Copy(GetVec(op.Rd), dRes); } } - private static void EmitSse2Scvtf(ArmEmitterContext context, bool scalar) + private static void EmitSse41Fcvts_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; Operand n = GetVec(op.Rn); - Operand res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); - - if (op is OpCodeSimdShImm fixedOp) + if (op.Size == 0) { - int fBits = GetImmShr(fixedOp); + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); - // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) - int fpScaled = 0x3F800000 - fBits * 0x800000; + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = X86GetScalar(context, fpScaled); - res = context.AddIntrinsic(Intrinsic.X86Mulps, res, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nInt); + + SetIntOrZR(context, op.Rd, dRes); } - - if (scalar) + else /* if (op.Size == 1) */ { - res = context.VectorZeroUpper96(res); - } - else if (op.RegisterSize == RegisterSize.Simd64) - { - res = context.VectorZeroUpper64(res); - } + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); - context.Copy(GetVec(op.Rd), res); + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nLong); + + SetIntOrZR(context, op.Rd, dRes); + } } - private static void EmitSse2Ucvtf(ArmEmitterContext context, bool scalar) + private static void EmitSse41Fcvtu_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) { - OpCodeSimd op = (OpCodeSimd)context.CurrOp; + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; Operand n = GetVec(op.Rn); - Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16)); - - res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res); - - Operand mask = X86GetAllElements(context, 0x47800000); // 65536.0f (1 << 16) - - res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask); - - Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16)); - - res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16)); - res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2); - - res = context.AddIntrinsic(Intrinsic.X86Addps, res, res2); - - if (op is OpCodeSimdShImm fixedOp) + if (op.Size == 0) { - int fBits = GetImmShr(fixedOp); + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); - // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) - int fpScaled = 0x3F800000 - fBits * 0x800000; + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; - Operand scale = X86GetAllElements(context, fpScaled); + Operand fpScaledMask = X86GetScalar(context, fpScaled); - res = context.AddIntrinsic(Intrinsic.X86Mulps, res, scale); + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subss, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nInt); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); } - - if (scalar) + else /* if (op.Size == 1) */ { - res = context.VectorZeroUpper96(res); - } - else if (op.RegisterSize == RegisterSize.Simd64) - { - res = context.VectorZeroUpper64(res); - } + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); - context.Copy(GetVec(op.Rd), res); + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subsd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nLong); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); + } } private static Operand EmitVectorLongExtract(ArmEmitterContext context, int reg, int index, int size) @@ -1121,14 +1455,5 @@ namespace ARMeilleure.Instructions return context.VectorExtract(type, GetVec(reg), index); } - - private static Operand EmitVectorLongCreate(ArmEmitterContext context, Operand low, Operand high) - { - Operand vector = context.VectorCreateScalar(low); - - vector = context.VectorInsert(vector, high, 1); - - return vector; - } } } diff --git a/ARMeilleure/Instructions/InstEmitSimdHelper.cs b/ARMeilleure/Instructions/InstEmitSimdHelper.cs index eebe155181..4155569de4 100644 --- a/ARMeilleure/Instructions/InstEmitSimdHelper.cs +++ b/ARMeilleure/Instructions/InstEmitSimdHelper.cs @@ -16,6 +16,24 @@ namespace ARMeilleure.Instructions static class InstEmitSimdHelper { +#region "Masks" + public static readonly long[] EvenMasks = new long[] + { + 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, // B + 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, // H + 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0 // S + }; + + public static readonly long[] OddMasks = new long[] + { + 15L << 56 | 13L << 48 | 11L << 40 | 09L << 32 | 07L << 24 | 05L << 16 | 03L << 8 | 01L << 0, // B + 15L << 56 | 14L << 48 | 11L << 40 | 10L << 32 | 07L << 24 | 06L << 16 | 03L << 8 | 02L << 0, // H + 15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0 // S + }; + + private static readonly long _zeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0; +#endregion + #region "X86 SSE Intrinsics" public static readonly Intrinsic[] X86PaddInstruction = new Intrinsic[] { @@ -189,11 +207,19 @@ namespace ARMeilleure.Instructions return vector; } + public static Operand X86GetElements(ArmEmitterContext context, long e1, long e0) + { + Operand vector0 = context.VectorCreateScalar(Const(e0)); + Operand vector1 = context.VectorCreateScalar(Const(e1)); + + return context.AddIntrinsic(Intrinsic.X86Punpcklqdq, vector0, vector1); + } + public static int X86GetRoundControl(FPRoundingMode roundMode) { switch (roundMode) { - case FPRoundingMode.ToNearest: return 8 | 0; + case FPRoundingMode.ToNearest: return 8 | 0; // even case FPRoundingMode.TowardsPlusInfinity: return 8 | 2; case FPRoundingMode.TowardsMinusInfinity: return 8 | 1; case FPRoundingMode.TowardsZero: return 8 | 3; @@ -948,6 +974,46 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } + public static void EmitSsse3VectorPairwiseOp(ArmEmitterContext context, Intrinsic[] inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand zeroEvenMask = X86GetElements(context, _zeroMask, EvenMasks[op.Size]); + Operand zeroOddMask = X86GetElements(context, _zeroMask, OddMasks [op.Size]); + + Operand mN = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); // m:n + + Operand left = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroEvenMask); // 0:even from m:n + Operand right = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroOddMask); // 0:odd from m:n + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else if (op.Size < 3) + { + Operand oddEvenMask = X86GetElements(context, OddMasks[op.Size], EvenMasks[op.Size]); + + Operand oddEvenN = context.AddIntrinsic(Intrinsic.X86Pshufb, n, oddEvenMask); // odd:even from n + Operand oddEvenM = context.AddIntrinsic(Intrinsic.X86Pshufb, m, oddEvenMask); // odd:even from m + + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, oddEvenN, oddEvenM); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, oddEvenN, oddEvenM); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else + { + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, n, m); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[3], left, right)); + } + } + public static void EmitVectorAcrossVectorOpSx(ArmEmitterContext context, Func2I emit) { EmitVectorAcrossVectorOp(context, emit, signed: true, isLong: false); @@ -1023,7 +1089,7 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } - public static void EmitVectorPairwiseOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + public static void EmitSse2VectorPairwiseOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) { OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; @@ -1272,7 +1338,9 @@ namespace ARMeilleure.Instructions int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); for (int index = 0; index < elems; index++) { @@ -1283,7 +1351,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, temp, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } // TSrc (16bit, 32bit, 64bit; signed, unsigned) > TDst (8bit, 16bit, 32bit; signed, unsigned). diff --git a/ARMeilleure/Instructions/InstEmitSimdLogical.cs b/ARMeilleure/Instructions/InstEmitSimdLogical.cs index 47529e7b68..362296f7ff 100644 --- a/ARMeilleure/Instructions/InstEmitSimdLogical.cs +++ b/ARMeilleure/Instructions/InstEmitSimdLogical.cs @@ -1,6 +1,7 @@ using ARMeilleure.Decoders; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.Translation; +using System.Diagnostics; using static ARMeilleure.Instructions.InstEmitHelper; using static ARMeilleure.Instructions.InstEmitSimdHelper; @@ -107,7 +108,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else { @@ -158,7 +159,7 @@ namespace ARMeilleure.Instructions res = context.VectorZeroUpper64(res); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else { @@ -292,11 +293,7 @@ namespace ARMeilleure.Instructions { Operand ne = EmitVectorExtractZx(context, op.Rn, index, 0); - ne = context.ConvertI64ToI32(ne); - - Operand de = context.SoftFallbackCall(nameof(SoftFallback.ReverseBits8), ne); - - de = context.ZeroExtend32(OperandType.I64, de); + Operand de = EmitReverseBits8Op(context, ne); res = EmitVectorInsert(context, res, de, index, 0); } @@ -304,6 +301,20 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } + private static Operand EmitReverseBits8Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaul)), Const(1)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x55ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccul)), Const(2)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x33ul)), Const(2))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(4)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0ful)), Const(4))); + } + public static void Rev16_V(ArmEmitterContext context) { if (Optimizations.UseSsse3) diff --git a/ARMeilleure/Instructions/InstEmitSimdMove.cs b/ARMeilleure/Instructions/InstEmitSimdMove.cs index dad66c06e2..31d40fda3f 100644 --- a/ARMeilleure/Instructions/InstEmitSimdMove.cs +++ b/ARMeilleure/Instructions/InstEmitSimdMove.cs @@ -12,20 +12,6 @@ namespace ARMeilleure.Instructions static partial class InstEmit { #region "Masks" - private static readonly long[] _masksE0_TrnUzpXtn = new long[] - { - 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, - 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, - 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0 - }; - - private static readonly long[] _masksE1_TrnUzp = new long[] - { - 15L << 56 | 13L << 48 | 11L << 40 | 09L << 32 | 07L << 24 | 05L << 16 | 03L << 8 | 01L << 0, - 15L << 56 | 14L << 48 | 11L << 40 | 10L << 32 | 07L << 24 | 06L << 16 | 03L << 8 | 02L << 0, - 15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0 - }; - private static readonly long[] _masksE0_Uzp = new long[] { 13L << 56 | 09L << 48 | 05L << 40 | 01L << 32 | 12L << 24 | 08L << 16 | 04L << 8 | 00L << 0, @@ -446,7 +432,7 @@ namespace ARMeilleure.Instructions Operand res = context.VectorZeroUpper64(d); - Operand mask = X86GetAllElements(context, _masksE0_TrnUzpXtn[op.Size]); + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, GetVec(op.Rn), mask); @@ -630,8 +616,8 @@ namespace ARMeilleure.Instructions if (op.Size < 3) { - long maskE0 = _masksE0_TrnUzpXtn[op.Size]; - long maskE1 = _masksE1_TrnUzp [op.Size]; + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks [op.Size]; mask = X86GetScalar(context, maskE0); @@ -698,8 +684,8 @@ namespace ARMeilleure.Instructions if (op.Size < 3) { - long maskE0 = _masksE0_TrnUzpXtn[op.Size]; - long maskE1 = _masksE1_TrnUzp [op.Size]; + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks [op.Size]; mask = X86GetScalar(context, maskE0); diff --git a/ARMeilleure/Instructions/InstEmitSimdShift.cs b/ARMeilleure/Instructions/InstEmitSimdShift.cs index be5104782f..6dbe66531e 100644 --- a/ARMeilleure/Instructions/InstEmitSimdShift.cs +++ b/ARMeilleure/Instructions/InstEmitSimdShift.cs @@ -4,6 +4,7 @@ using ARMeilleure.Decoders; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.Translation; using System; +using System.Diagnostics; using static ARMeilleure.Instructions.InstEmitHelper; using static ARMeilleure.Instructions.InstEmitSimdHelper; @@ -16,13 +17,6 @@ namespace ARMeilleure.Instructions static partial class InstEmit { #region "Masks" - private static readonly long[] _masks_RshrnShrn = new long[] - { - 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, - 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, - 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0 - }; - private static readonly long[] _masks_SliSri = new long[] // Replication masks. { 0x0101010101010101L, 0x0001000100010001L, 0x0000000100000001L, 0x0000000000000001L @@ -42,7 +36,7 @@ namespace ARMeilleure.Instructions Operand d = GetVec(op.Rd); Operand n = GetVec(op.Rn); - Operand dLow = context.AddIntrinsic(Intrinsic.X86Movlhps, d, context.VectorZero()); + Operand dLow = context.VectorZeroUpper64(d); Operand mask = null; @@ -61,7 +55,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(srlInst, res, Const(shift)); - Operand mask2 = X86GetAllElements(context, _masks_RshrnShrn[op.Size]); + Operand mask2 = X86GetAllElements(context, EvenMasks[op.Size]); res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask2); @@ -157,13 +151,13 @@ namespace ARMeilleure.Instructions Operand d = GetVec(op.Rd); Operand n = GetVec(op.Rn); - Operand dLow = context.AddIntrinsic(Intrinsic.X86Movlhps, d, context.VectorZero()); + Operand dLow = context.VectorZeroUpper64(d); Intrinsic srlInst = X86PsrlInstruction[op.Size + 1]; Operand nShifted = context.AddIntrinsic(srlInst, n, Const(shift)); - Operand mask = X86GetAllElements(context, _masks_RshrnShrn[op.Size]); + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, nShifted, mask); @@ -702,9 +696,9 @@ namespace ARMeilleure.Instructions for (int index = 0; index < elems; index++) { Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); - Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + Operand me = EmitVectorExtractSx(context, op.Rm, index << op.Size, 0); - Operand e = context.SoftFallbackCall(nameof(SoftFallback.UnsignedShlReg), ne, me, Const(0), Const(op.Size)); + Operand e = EmitUnsignedShlRegOp(context, ne, context.ConvertI64ToI32(me), op.Size); res = EmitVectorInsert(context, res, e, index, op.Size); } @@ -879,9 +873,7 @@ namespace ARMeilleure.Instructions e = context.Add(e, Const(roundConst)); } - e = signed - ? context.ShiftRightSI(e, Const(shift)) - : context.ShiftRightUI(e, Const(shift)); + e = signed ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); } else /* if (op.Size == 3) */ { @@ -901,6 +893,28 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } + private static Operand EmitUnsignedShlRegOp(ArmEmitterContext context, Operand op, Operand shiftLsB, int size) + { + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + Debug.Assert((uint)size < 4u); + + Operand negShiftLsB = context.Negate(shiftLsB); + + Operand isPositive = context.ICompareGreaterOrEqual(shiftLsB, Const(0)); + + Operand shl = context.ShiftLeft (op, shiftLsB); + Operand shr = context.ShiftRightUI(op, negShiftLsB); + + Operand res = context.ConditionalSelect(isPositive, shl, shr); + + Operand isOutOfRange = context.BitwiseOr( + context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)), + context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size))); + + return context.ConditionalSelect(isOutOfRange, Const(0UL), res); + } + private static void EmitVectorShrImmNarrowOpZx(ArmEmitterContext context, bool round) { OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; @@ -913,7 +927,9 @@ namespace ARMeilleure.Instructions int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); for (int index = 0; index < elems; index++) { @@ -929,7 +945,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, e, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } [Flags] @@ -972,7 +988,9 @@ namespace ARMeilleure.Instructions int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; - Operand res = part == 0 ? context.VectorZero() : context.Copy(GetVec(op.Rd)); + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); for (int index = 0; index < elems; index++) { @@ -985,9 +1003,7 @@ namespace ARMeilleure.Instructions e = context.Add(e, Const(roundConst)); } - e = signedSrc - ? context.ShiftRightSI(e, Const(shift)) - : context.ShiftRightUI(e, Const(shift)); + e = signedSrc ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); } else /* if (op.Size == 2 && round) */ { @@ -999,7 +1015,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, e, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } // dst64 = (Int(src64, signed) + roundConst) >> shift; diff --git a/ARMeilleure/Instructions/SoftFallback.cs b/ARMeilleure/Instructions/SoftFallback.cs index afdb76b47c..50e43bc010 100644 --- a/ARMeilleure/Instructions/SoftFallback.cs +++ b/ARMeilleure/Instructions/SoftFallback.cs @@ -1241,75 +1241,6 @@ namespace ARMeilleure.Instructions } #endregion -#region "Reverse" - public static uint ReverseBits8(uint value) - { - value = ((value & 0xaa) >> 1) | ((value & 0x55) << 1); - value = ((value & 0xcc) >> 2) | ((value & 0x33) << 2); - - return (value >> 4) | ((value & 0x0f) << 4); - } - - public static uint ReverseBits32(uint value) - { - value = ((value & 0xaaaaaaaa) >> 1) | ((value & 0x55555555) << 1); - value = ((value & 0xcccccccc) >> 2) | ((value & 0x33333333) << 2); - value = ((value & 0xf0f0f0f0) >> 4) | ((value & 0x0f0f0f0f) << 4); - value = ((value & 0xff00ff00) >> 8) | ((value & 0x00ff00ff) << 8); - - return (value >> 16) | (value << 16); - } - - public static ulong ReverseBits64(ulong value) - { - value = ((value & 0xaaaaaaaaaaaaaaaa) >> 1 ) | ((value & 0x5555555555555555) << 1 ); - value = ((value & 0xcccccccccccccccc) >> 2 ) | ((value & 0x3333333333333333) << 2 ); - value = ((value & 0xf0f0f0f0f0f0f0f0) >> 4 ) | ((value & 0x0f0f0f0f0f0f0f0f) << 4 ); - value = ((value & 0xff00ff00ff00ff00) >> 8 ) | ((value & 0x00ff00ff00ff00ff) << 8 ); - value = ((value & 0xffff0000ffff0000) >> 16) | ((value & 0x0000ffff0000ffff) << 16); - - return (value >> 32) | (value << 32); - } - - public static uint ReverseBytes16_32(uint value) => (uint)ReverseBytes16_64(value); - - public static ulong ReverseBytes16_64(ulong value) => ReverseBytes(value, RevSize.Rev16); - public static ulong ReverseBytes32_64(ulong value) => ReverseBytes(value, RevSize.Rev32); - - private enum RevSize - { - Rev16, - Rev32, - Rev64 - } - - private static ulong ReverseBytes(ulong value, RevSize size) - { - value = ((value & 0xff00ff00ff00ff00) >> 8) | ((value & 0x00ff00ff00ff00ff) << 8); - - if (size == RevSize.Rev16) - { - return value; - } - - value = ((value & 0xffff0000ffff0000) >> 16) | ((value & 0x0000ffff0000ffff) << 16); - - if (size == RevSize.Rev32) - { - return value; - } - - value = ((value & 0xffffffff00000000) >> 32) | ((value & 0x00000000ffffffff) << 32); - - if (size == RevSize.Rev64) - { - return value; - } - - throw new ArgumentException(nameof(size)); - } -#endregion - #region "MaxMin" public static long MaxS64(long val1, long val2) => (val1 >= val2) ? val1 : val2; public static ulong MaxU64(ulong val1, ulong val2) => (val1 >= val2) ? val1 : val2; diff --git a/ARMeilleure/IntermediateRepresentation/Intrinsic.cs b/ARMeilleure/IntermediateRepresentation/Intrinsic.cs index 57c8914d9c..c3f375c4c2 100644 --- a/ARMeilleure/IntermediateRepresentation/Intrinsic.cs +++ b/ARMeilleure/IntermediateRepresentation/Intrinsic.cs @@ -30,8 +30,11 @@ namespace ARMeilleure.IntermediateRepresentation X86Cvtps2pd, X86Cvtsd2si, X86Cvtsd2ss, + X86Cvtsi2sd, X86Cvtsi2si, + X86Cvtsi2ss, X86Cvtss2sd, + X86Cvtss2si, X86Divpd, X86Divps, X86Divsd, diff --git a/Ryujinx/Configuration.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs similarity index 53% rename from Ryujinx/Configuration.cs rename to Ryujinx.Common/Configuration/ConfigurationFileFormat.cs index c259f9e9d4..1a9407cb2f 100644 --- a/Ryujinx/Configuration.cs +++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs @@ -1,30 +1,22 @@ using JsonPrettyPrinterPlus; -using LibHac.FsSystem; -using OpenTK.Input; -using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.HLE; -using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.HOS.Services; -using Ryujinx.HLE.Input; -using Ryujinx.Ui; -using Ryujinx.Ui.Input; using System; using System.Collections.Generic; using System.IO; using System.Text; -using System.Threading.Tasks; using Utf8Json; using Utf8Json.Resolvers; +using Ryujinx.Configuration.System; +using Ryujinx.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.UI.Input; +using Ryujinx.Configuration.Ui; -namespace Ryujinx +namespace Ryujinx.Configuration { - public class Configuration + public class ConfigurationFileFormat { - /// - /// The default configuration instance - /// - public static Configuration Instance { get; private set; } + public int Version { get; set; } /// /// Dumps shaders in this local directory @@ -79,7 +71,7 @@ namespace Ryujinx /// /// Change System Language /// - public SystemLanguage SystemLanguage { get; set; } + public Language SystemLanguage { get; set; } /// /// Enables or disables Docked Mode @@ -119,7 +111,7 @@ namespace Ryujinx /// /// The primary controller's type /// - public ControllerStatus ControllerType { get; set; } + public ControllerType ControllerType { get; set; } /// /// Used to toggle columns in the GUI @@ -154,13 +146,13 @@ namespace Ryujinx /// /// Controller control bindings /// - public Ui.Input.NpadController JoystickControls { get; private set; } + public NpadController JoystickControls { get; set; } /// /// Loads a configuration file from disk /// /// The path to the JSON configuration file - public static void Load(string path) + public static ConfigurationFileFormat Load(string path) { var resolver = CompositeResolver.Create( new[] { new ConfigurationEnumFormatter() }, @@ -169,24 +161,7 @@ namespace Ryujinx using (Stream stream = File.OpenRead(path)) { - Instance = JsonSerializer.Deserialize(stream, resolver); - } - } - - /// - /// Loads a configuration file asynchronously from disk - /// - /// The path to the JSON configuration file - public static async Task LoadAsync(string path) - { - IJsonFormatterResolver resolver = CompositeResolver.Create( - new[] { new ConfigurationEnumFormatter() }, - new[] { StandardResolver.AllowPrivateSnakeCase } - ); - - using (Stream stream = File.OpenRead(path)) - { - Instance = await JsonSerializer.DeserializeAsync(stream, resolver); + return JsonSerializer.Deserialize(stream, resolver); } } @@ -194,108 +169,17 @@ namespace Ryujinx /// Save a configuration file to disk /// /// The path to the JSON configuration file - public static void SaveConfig(Configuration config, string path) + public void SaveConfig(string path) { IJsonFormatterResolver resolver = CompositeResolver.Create( new[] { new ConfigurationEnumFormatter() }, new[] { StandardResolver.AllowPrivateSnakeCase } ); - byte[] data = JsonSerializer.Serialize(config, resolver); + byte[] data = JsonSerializer.Serialize(this, resolver); File.WriteAllText(path, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson()); } - /// - /// Configures a instance - /// - /// The instance to configure - public static void InitialConfigure(Switch device) - { - if (Instance == null) - { - throw new InvalidOperationException("Configuration has not been loaded yet."); - } - - SwitchSettings.ConfigureSettings(Instance); - - Logger.AddTarget(new AsyncLogTargetWrapper( - new ConsoleLogTarget(), - 1000, - AsyncLogTargetOverflowAction.Block - )); - - if (Instance.EnableFileLog) - { - Logger.AddTarget(new AsyncLogTargetWrapper( - new FileLogTarget(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.log")), - 1000, - AsyncLogTargetOverflowAction.Block - )); - } - - Configure(device, Instance); - } - - public static void Configure(Switch device, Configuration SwitchConfig) - { - GraphicsConfig.ShadersDumpPath = SwitchConfig.GraphicsShadersDumpPath; - - Logger.SetEnable(LogLevel.Debug, SwitchConfig.LoggingEnableDebug ); - Logger.SetEnable(LogLevel.Stub, SwitchConfig.LoggingEnableStub ); - Logger.SetEnable(LogLevel.Info, SwitchConfig.LoggingEnableInfo ); - Logger.SetEnable(LogLevel.Warning, SwitchConfig.LoggingEnableWarn ); - Logger.SetEnable(LogLevel.Error, SwitchConfig.LoggingEnableError ); - Logger.SetEnable(LogLevel.Guest, SwitchConfig.LoggingEnableGuest ); - Logger.SetEnable(LogLevel.AccessLog, SwitchConfig.LoggingEnableFsAccessLog); - - if (SwitchConfig.LoggingFilteredClasses.Length > 0) - { - foreach (var logClass in EnumExtensions.GetValues()) - { - Logger.SetEnable(logClass, false); - } - - foreach (var logClass in SwitchConfig.LoggingFilteredClasses) - { - Logger.SetEnable(logClass, true); - } - } - - MainWindow.DiscordIntegrationEnabled = SwitchConfig.EnableDiscordIntegration; - - device.EnableDeviceVsync = SwitchConfig.EnableVsync; - - device.System.State.DockedMode = SwitchConfig.DockedMode; - - device.System.State.SetLanguage(SwitchConfig.SystemLanguage); - - if (SwitchConfig.EnableMulticoreScheduling) - { - device.System.EnableMultiCoreScheduling(); - } - - device.System.FsIntegrityCheckLevel = SwitchConfig.EnableFsIntegrityChecks - ? IntegrityCheckLevel.ErrorOnInvalid - : IntegrityCheckLevel.None; - - device.System.GlobalAccessLogMode = SwitchConfig.FsGlobalAccessLogMode; - - ServiceConfiguration.IgnoreMissingServices = SwitchConfig.IgnoreMissingServices; - } - - public static void ConfigureHid(Switch device, Configuration SwitchConfig) - { - if (SwitchConfig.JoystickControls.Enabled) - { - if (!Joystick.GetState(SwitchConfig.JoystickControls.Index).IsConnected) - { - SwitchConfig.JoystickControls.SetEnabled(false); - } - } - device.Hid.InitializePrimaryController(SwitchConfig.ControllerType); - device.Hid.InitializeKeyboard(); - } - private class ConfigurationEnumFormatter : IJsonFormatter where T : struct { diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs new file mode 100644 index 0000000000..050b497385 --- /dev/null +++ b/Ryujinx.Common/Configuration/ConfigurationState.cs @@ -0,0 +1,502 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Logging; +using Ryujinx.Configuration.Hid; +using Ryujinx.Configuration.System; +using Ryujinx.Configuration.Ui; +using Ryujinx.UI.Input; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Configuration +{ + public class ConfigurationState + { + /// + /// UI configuration section + /// + public class UiSection + { + public class Columns + { + public ReactiveObject FavColumn { get; private set; } + public ReactiveObject IconColumn { get; private set; } + public ReactiveObject AppColumn { get; private set; } + public ReactiveObject DevColumn { get; private set; } + public ReactiveObject VersionColumn { get; private set; } + public ReactiveObject TimePlayedColumn { get; private set; } + public ReactiveObject LastPlayedColumn { get; private set; } + public ReactiveObject FileExtColumn { get; private set; } + public ReactiveObject FileSizeColumn { get; private set; } + public ReactiveObject PathColumn { get; private set; } + + public Columns() + { + FavColumn = new ReactiveObject(); + IconColumn = new ReactiveObject(); + AppColumn = new ReactiveObject(); + DevColumn = new ReactiveObject(); + VersionColumn = new ReactiveObject(); + TimePlayedColumn = new ReactiveObject(); + LastPlayedColumn = new ReactiveObject(); + FileExtColumn = new ReactiveObject(); + FileSizeColumn = new ReactiveObject(); + PathColumn = new ReactiveObject(); + } + } + + /// + /// Used to toggle columns in the GUI + /// + public Columns GuiColumns { get; private set; } + + /// + /// A list of directories containing games to be used to load games into the games list + /// + public ReactiveObject> GameDirs { get; private set; } + + /// + /// Enable or disable custom themes in the GUI + /// + public ReactiveObject EnableCustomTheme { get; private set; } + + /// + /// Path to custom GUI theme + /// + public ReactiveObject CustomThemePath { get; private set; } + + public UiSection() + { + GuiColumns = new Columns(); + GameDirs = new ReactiveObject>(); + EnableCustomTheme = new ReactiveObject(); + CustomThemePath = new ReactiveObject(); + } + } + + /// + /// Logger configuration section + /// + public class LoggerSection + { + /// + /// Enables printing debug log messages + /// + public ReactiveObject EnableDebug { get; private set; } + + /// + /// Enables printing stub log messages + /// + public ReactiveObject EnableStub { get; private set; } + + /// + /// Enables printing info log messages + /// + public ReactiveObject EnableInfo { get; private set; } + + /// + /// Enables printing warning log messages + /// + public ReactiveObject EnableWarn { get; private set; } + + /// + /// Enables printing error log messages + /// + public ReactiveObject EnableError { get; private set; } + + /// + /// Enables printing guest log messages + /// + public ReactiveObject EnableGuest { get; private set; } + + /// + /// Enables printing FS access log messages + /// + public ReactiveObject EnableFsAccessLog { get; private set; } + + /// + /// Controls which log messages are written to the log targets + /// + public ReactiveObject FilteredClasses { get; private set; } + + /// + /// Enables or disables logging to a file on disk + /// + public ReactiveObject EnableFileLog { get; private set; } + + public LoggerSection() + { + EnableDebug = new ReactiveObject(); + EnableStub = new ReactiveObject(); + EnableInfo = new ReactiveObject(); + EnableWarn = new ReactiveObject(); + EnableError = new ReactiveObject(); + EnableGuest = new ReactiveObject(); + EnableFsAccessLog = new ReactiveObject(); + FilteredClasses = new ReactiveObject(); + EnableFileLog = new ReactiveObject(); + } + } + + /// + /// System configuration section + /// + public class SystemSection + { + /// + /// Change System Language + /// + public ReactiveObject Language { get; private set; } + + /// + /// Enables or disables Docked Mode + /// + public ReactiveObject EnableDockedMode { get; private set; } + + /// + /// Enables or disables multi-core scheduling of threads + /// + public ReactiveObject EnableMulticoreScheduling { get; private set; } + + /// + /// Enables integrity checks on Game content files + /// + public ReactiveObject EnableFsIntegrityChecks { get; private set; } + + /// + /// Enables FS access log output to the console. Possible modes are 0-3 + /// + public ReactiveObject FsGlobalAccessLogMode { get; private set; } + + /// + /// Enable or disable ignoring missing services + /// + public ReactiveObject IgnoreMissingServices { get; private set; } + + public SystemSection() + { + Language = new ReactiveObject(); + EnableDockedMode = new ReactiveObject(); + EnableMulticoreScheduling = new ReactiveObject(); + EnableFsIntegrityChecks = new ReactiveObject(); + FsGlobalAccessLogMode = new ReactiveObject(); + IgnoreMissingServices = new ReactiveObject(); + } + } + + /// + /// Hid configuration section + /// + public class HidSection + { + /// + /// The primary controller's type + /// + public ReactiveObject ControllerType { get; private set; } + + /// + /// Enable or disable keyboard support (Independent from controllers binding) + /// + public ReactiveObject EnableKeyboard { get; private set; } + + /// + /// Keyboard control bindings + /// + public ReactiveObject KeyboardControls { get; private set; } + + /// + /// Controller control bindings + /// + public ReactiveObject JoystickControls { get; private set; } + + public HidSection() + { + ControllerType = new ReactiveObject(); + EnableKeyboard = new ReactiveObject(); + KeyboardControls = new ReactiveObject(); + JoystickControls = new ReactiveObject(); + } + } + + /// + /// Graphics configuration section + /// + public class GraphicsSection + { + /// + /// Dumps shaders in this local directory + /// + public ReactiveObject ShadersDumpPath { get; private set; } + + /// + /// Enables or disables Vertical Sync + /// + public ReactiveObject EnableVsync { get; private set; } + + public GraphicsSection() + { + ShadersDumpPath = new ReactiveObject(); + EnableVsync = new ReactiveObject(); + } + } + + /// + /// The default configuration instance + /// + public static ConfigurationState Instance { get; private set; } + + /// + /// The Ui section + /// + public UiSection Ui { get; private set; } + + /// + /// The Logger section + /// + public LoggerSection Logger { get; private set; } + + /// + /// The System section + /// + public SystemSection System { get; private set; } + + /// + /// The Graphics section + /// + public GraphicsSection Graphics { get; private set; } + + /// + /// The Hid section + /// + public HidSection Hid { get; private set; } + + /// + /// Enables or disables Discord Rich Presence + /// + public ReactiveObject EnableDiscordIntegration { get; private set; } + + private ConfigurationState() + { + Ui = new UiSection(); + Logger = new LoggerSection(); + System = new SystemSection(); + Graphics = new GraphicsSection(); + Hid = new HidSection(); + EnableDiscordIntegration = new ReactiveObject(); + } + + public ConfigurationFileFormat ToFileFormat() + { + ConfigurationFileFormat configurationFile = new ConfigurationFileFormat + { + Version = 1, + GraphicsShadersDumpPath = Graphics.ShadersDumpPath, + LoggingEnableDebug = Logger.EnableDebug, + LoggingEnableStub = Logger.EnableStub, + LoggingEnableInfo = Logger.EnableInfo, + LoggingEnableWarn = Logger.EnableWarn, + LoggingEnableError = Logger.EnableError, + LoggingEnableGuest = Logger.EnableGuest, + LoggingEnableFsAccessLog = Logger.EnableFsAccessLog, + LoggingFilteredClasses = Logger.FilteredClasses, + EnableFileLog = Logger.EnableFileLog, + SystemLanguage = System.Language, + DockedMode = System.EnableDockedMode, + EnableDiscordIntegration = EnableDiscordIntegration, + EnableVsync = Graphics.EnableVsync, + EnableMulticoreScheduling = System.EnableMulticoreScheduling, + EnableFsIntegrityChecks = System.EnableFsIntegrityChecks, + FsGlobalAccessLogMode = System.FsGlobalAccessLogMode, + IgnoreMissingServices = System.IgnoreMissingServices, + ControllerType = Hid.ControllerType, + GuiColumns = new GuiColumns() + { + FavColumn = Ui.GuiColumns.FavColumn, + IconColumn = Ui.GuiColumns.IconColumn, + AppColumn = Ui.GuiColumns.AppColumn, + DevColumn = Ui.GuiColumns.DevColumn, + VersionColumn = Ui.GuiColumns.VersionColumn, + TimePlayedColumn = Ui.GuiColumns.TimePlayedColumn, + LastPlayedColumn = Ui.GuiColumns.LastPlayedColumn, + FileExtColumn = Ui.GuiColumns.FileExtColumn, + FileSizeColumn = Ui.GuiColumns.FileSizeColumn, + PathColumn = Ui.GuiColumns.PathColumn, + }, + GameDirs = Ui.GameDirs, + EnableCustomTheme = Ui.EnableCustomTheme, + CustomThemePath = Ui.CustomThemePath, + EnableKeyboard = Hid.EnableKeyboard, + KeyboardControls = Hid.KeyboardControls, + JoystickControls = Hid.JoystickControls + }; + + return configurationFile; + } + + public void LoadDefault() + { + Graphics.ShadersDumpPath.Value = ""; + Logger.EnableDebug.Value = false; + Logger.EnableStub.Value = true; + Logger.EnableInfo.Value = true; + Logger.EnableWarn.Value = true; + Logger.EnableError.Value = true; + Logger.EnableGuest.Value = true; + Logger.EnableFsAccessLog.Value = false; + Logger.FilteredClasses.Value = new LogClass[] { }; + Logger.EnableFileLog.Value = true; + System.Language.Value = Language.AmericanEnglish; + System.EnableDockedMode.Value = false; + EnableDiscordIntegration.Value = true; + Graphics.EnableVsync.Value = true; + System.EnableMulticoreScheduling.Value = true; + System.EnableFsIntegrityChecks.Value = true; + System.FsGlobalAccessLogMode.Value = 0; + System.IgnoreMissingServices.Value = false; + Hid.ControllerType.Value = ControllerType.Handheld; + Ui.GuiColumns.FavColumn.Value = true; + Ui.GuiColumns.IconColumn.Value = true; + Ui.GuiColumns.AppColumn.Value = true; + Ui.GuiColumns.DevColumn.Value = true; + Ui.GuiColumns.VersionColumn.Value = true; + Ui.GuiColumns.TimePlayedColumn.Value = true; + Ui.GuiColumns.LastPlayedColumn.Value = true; + Ui.GuiColumns.FileExtColumn.Value = true; + Ui.GuiColumns.FileSizeColumn.Value = true; + Ui.GuiColumns.PathColumn.Value = true; + Ui.GameDirs.Value = new List(); + Ui.EnableCustomTheme.Value = false; + Ui.CustomThemePath.Value = ""; + Hid.EnableKeyboard.Value = false; + + Hid.KeyboardControls.Value = new NpadKeyboard + { + LeftJoycon = new NpadKeyboardLeft + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + DPadUp = Key.Up, + DPadDown = Key.Down, + DPadLeft = Key.Left, + DPadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + }, + RightJoycon = new NpadKeyboardRight + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + }, + Hotkeys = new KeyboardHotkeys + { + ToggleVsync = Key.Tab + } + }; + + Hid.JoystickControls.Value = new NpadController + { + Enabled = true, + Index = 0, + Deadzone = 0.05f, + TriggerThreshold = 0.5f, + LeftJoycon = new NpadControllerLeft + { + Stick = ControllerInputId.Axis0, + StickButton = ControllerInputId.Button8, + DPadUp = ControllerInputId.Hat0Up, + DPadDown = ControllerInputId.Hat0Down, + DPadLeft = ControllerInputId.Hat0Left, + DPadRight = ControllerInputId.Hat0Right, + ButtonMinus = ControllerInputId.Button6, + ButtonL = ControllerInputId.Button4, + ButtonZl = ControllerInputId.Axis2, + }, + RightJoycon = new NpadControllerRight + { + Stick = ControllerInputId.Axis3, + StickButton = ControllerInputId.Button9, + ButtonA = ControllerInputId.Button1, + ButtonB = ControllerInputId.Button0, + ButtonX = ControllerInputId.Button3, + ButtonY = ControllerInputId.Button2, + ButtonPlus = ControllerInputId.Button7, + ButtonR = ControllerInputId.Button5, + ButtonZr = ControllerInputId.Axis5, + } + }; + } + + public void Load(ConfigurationFileFormat configurationFileFormat) + { + if (configurationFileFormat.Version != 1 && configurationFileFormat.Version != 0) + { + Common.Logging.Logger.PrintWarning(LogClass.Application, $"Unsupported configuration version {configurationFileFormat.Version}, loading default."); + + LoadDefault(); + + return; + } + + Graphics.ShadersDumpPath.Value = configurationFileFormat.GraphicsShadersDumpPath; + Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug; + Logger.EnableStub.Value = configurationFileFormat.LoggingEnableStub; + Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo; + Logger.EnableWarn.Value = configurationFileFormat.LoggingEnableWarn; + Logger.EnableError.Value = configurationFileFormat.LoggingEnableError; + Logger.EnableGuest.Value = configurationFileFormat.LoggingEnableGuest; + Logger.EnableFsAccessLog.Value = configurationFileFormat.LoggingEnableFsAccessLog; + Logger.FilteredClasses.Value = configurationFileFormat.LoggingFilteredClasses; + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; + System.Language.Value = configurationFileFormat.SystemLanguage; + System.EnableDockedMode.Value = configurationFileFormat.DockedMode; + System.EnableDockedMode.Value = configurationFileFormat.DockedMode; + EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; + Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; + System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling; + System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks; + System.FsGlobalAccessLogMode.Value = configurationFileFormat.FsGlobalAccessLogMode; + System.IgnoreMissingServices.Value = configurationFileFormat.IgnoreMissingServices; + Hid.ControllerType.Value = configurationFileFormat.ControllerType; + Ui.GuiColumns.FavColumn.Value = configurationFileFormat.GuiColumns.FavColumn; + Ui.GuiColumns.IconColumn.Value = configurationFileFormat.GuiColumns.IconColumn; + Ui.GuiColumns.AppColumn.Value = configurationFileFormat.GuiColumns.AppColumn; + Ui.GuiColumns.DevColumn.Value = configurationFileFormat.GuiColumns.DevColumn; + Ui.GuiColumns.VersionColumn.Value = configurationFileFormat.GuiColumns.VersionColumn; + Ui.GuiColumns.TimePlayedColumn.Value = configurationFileFormat.GuiColumns.TimePlayedColumn; + Ui.GuiColumns.LastPlayedColumn.Value = configurationFileFormat.GuiColumns.LastPlayedColumn; + Ui.GuiColumns.FileExtColumn.Value = configurationFileFormat.GuiColumns.FileExtColumn; + Ui.GuiColumns.FileSizeColumn.Value = configurationFileFormat.GuiColumns.FileSizeColumn; + Ui.GuiColumns.PathColumn.Value = configurationFileFormat.GuiColumns.PathColumn; + Ui.GameDirs.Value = configurationFileFormat.GameDirs; + Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme; + Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath; + Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard; + Hid.KeyboardControls.Value = configurationFileFormat.KeyboardControls; + Hid.JoystickControls.Value = configurationFileFormat.JoystickControls; + } + + public static void Initialize() + { + if (Instance != null) + { + throw new InvalidOperationException("Configuration is already initialized"); + } + + Instance = new ConfigurationState(); + } + } +} diff --git a/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs b/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs new file mode 100644 index 0000000000..8969b6a4b8 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public enum ControllerInputId + { + Button0, + Button1, + Button2, + Button3, + Button4, + Button5, + Button6, + Button7, + Button8, + Button9, + Button10, + Button11, + Button12, + Button13, + Button14, + Button15, + Button16, + Button17, + Button18, + Button19, + Button20, + Axis0, + Axis1, + Axis2, + Axis3, + Axis4, + Axis5, + Hat0Up, + Hat0Down, + Hat0Left, + Hat0Right, + Hat1Up, + Hat1Down, + Hat1Left, + Hat1Right, + Hat2Up, + Hat2Down, + Hat2Left, + Hat2Right + } +} diff --git a/Ryujinx.Common/Configuration/Hid/ControllerType.cs b/Ryujinx.Common/Configuration/Hid/ControllerType.cs new file mode 100644 index 0000000000..b0613b2d66 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/ControllerType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Configuration.Hid +{ + public enum ControllerType + { + ProController, + Handheld, + NpadPair, + NpadLeft, + NpadRight + } +} diff --git a/Ryujinx.Common/Configuration/Hid/Key.cs b/Ryujinx.Common/Configuration/Hid/Key.cs new file mode 100644 index 0000000000..b658396b9b --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/Key.cs @@ -0,0 +1,153 @@ +namespace Ryujinx.Configuration.Hid +{ + public enum Key + { + Unknown = 0, + ShiftLeft = 1, + LShift = 1, + ShiftRight = 2, + RShift = 2, + ControlLeft = 3, + LControl = 3, + ControlRight = 4, + RControl = 4, + AltLeft = 5, + LAlt = 5, + AltRight = 6, + RAlt = 6, + WinLeft = 7, + LWin = 7, + WinRight = 8, + RWin = 8, + Menu = 9, + F1 = 10, + F2 = 11, + F3 = 12, + F4 = 13, + F5 = 14, + F6 = 15, + F7 = 16, + F8 = 17, + F9 = 18, + F10 = 19, + F11 = 20, + F12 = 21, + F13 = 22, + F14 = 23, + F15 = 24, + F16 = 25, + F17 = 26, + F18 = 27, + F19 = 28, + F20 = 29, + F21 = 30, + F22 = 31, + F23 = 32, + F24 = 33, + F25 = 34, + F26 = 35, + F27 = 36, + F28 = 37, + F29 = 38, + F30 = 39, + F31 = 40, + F32 = 41, + F33 = 42, + F34 = 43, + F35 = 44, + Up = 45, + Down = 46, + Left = 47, + Right = 48, + Enter = 49, + Escape = 50, + Space = 51, + Tab = 52, + BackSpace = 53, + Back = 53, + Insert = 54, + Delete = 55, + PageUp = 56, + PageDown = 57, + Home = 58, + End = 59, + CapsLock = 60, + ScrollLock = 61, + PrintScreen = 62, + Pause = 63, + NumLock = 64, + Clear = 65, + Sleep = 66, + Keypad0 = 67, + Keypad1 = 68, + Keypad2 = 69, + Keypad3 = 70, + Keypad4 = 71, + Keypad5 = 72, + Keypad6 = 73, + Keypad7 = 74, + Keypad8 = 75, + Keypad9 = 76, + KeypadDivide = 77, + KeypadMultiply = 78, + KeypadSubtract = 79, + KeypadMinus = 79, + KeypadAdd = 80, + KeypadPlus = 80, + KeypadDecimal = 81, + KeypadPeriod = 81, + KeypadEnter = 82, + A = 83, + B = 84, + C = 85, + D = 86, + E = 87, + F = 88, + G = 89, + H = 90, + I = 91, + J = 92, + K = 93, + L = 94, + M = 95, + N = 96, + O = 97, + P = 98, + Q = 99, + R = 100, + S = 101, + T = 102, + U = 103, + V = 104, + W = 105, + X = 106, + Y = 107, + Z = 108, + Number0 = 109, + Number1 = 110, + Number2 = 111, + Number3 = 112, + Number4 = 113, + Number5 = 114, + Number6 = 115, + Number7 = 116, + Number8 = 117, + Number9 = 118, + Tilde = 119, + Grave = 119, + Minus = 120, + Plus = 121, + BracketLeft = 122, + LBracket = 122, + BracketRight = 123, + RBracket = 123, + Semicolon = 124, + Quote = 125, + Comma = 126, + Period = 127, + Slash = 128, + BackSlash = 129, + NonUSBackSlash = 130, + LastKey = 131 + } +} diff --git a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs new file mode 100644 index 0000000000..1d0b050492 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct KeyboardHotkeys + { + public Key ToggleVsync; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadController.cs b/Ryujinx.Common/Configuration/Hid/NpadController.cs new file mode 100644 index 0000000000..f00865d556 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadController.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public class NpadController + { + /// + /// Enables or disables controller support + /// + public bool Enabled; + + /// + /// Controller Device Index + /// + public int Index; + + /// + /// Controller Analog Stick Deadzone + /// + public float Deadzone; + + /// + /// Controller Trigger Threshold + /// + public float TriggerThreshold; + + /// + /// Left JoyCon Controller Bindings + /// + public NpadControllerLeft LeftJoycon; + + /// + /// Right JoyCon Controller Bindings + /// + public NpadControllerRight RightJoycon; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs b/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs new file mode 100644 index 0000000000..54ac0f03ae --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public struct NpadControllerLeft + { + public ControllerInputId Stick; + public ControllerInputId StickButton; + public ControllerInputId ButtonMinus; + public ControllerInputId ButtonL; + public ControllerInputId ButtonZl; + public ControllerInputId DPadUp; + public ControllerInputId DPadDown; + public ControllerInputId DPadLeft; + public ControllerInputId DPadRight; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs b/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs new file mode 100644 index 0000000000..315136d9f6 --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public struct NpadControllerRight + { + public ControllerInputId Stick; + public ControllerInputId StickButton; + public ControllerInputId ButtonA; + public ControllerInputId ButtonB; + public ControllerInputId ButtonX; + public ControllerInputId ButtonY; + public ControllerInputId ButtonPlus; + public ControllerInputId ButtonR; + public ControllerInputId ButtonZr; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs new file mode 100644 index 0000000000..911f5119ea --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboard.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.UI.Input +{ + public class NpadKeyboard + { + /// + /// Left JoyCon Keyboard Bindings + /// + public Configuration.Hid.NpadKeyboardLeft LeftJoycon; + + /// + /// Right JoyCon Keyboard Bindings + /// + public Configuration.Hid.NpadKeyboardRight RightJoycon; + + /// + /// Hotkey Keyboard Bindings + /// + public Configuration.Hid.KeyboardHotkeys Hotkeys; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs new file mode 100644 index 0000000000..799cdfdb8a --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct NpadKeyboardLeft + { + public Key StickUp; + public Key StickDown; + public Key StickLeft; + public Key StickRight; + public Key StickButton; + public Key DPadUp; + public Key DPadDown; + public Key DPadLeft; + public Key DPadRight; + public Key ButtonMinus; + public Key ButtonL; + public Key ButtonZl; + } +} diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs new file mode 100644 index 0000000000..311504bb7e --- /dev/null +++ b/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Configuration.Hid +{ + public struct NpadKeyboardRight + { + public Key StickUp; + public Key StickDown; + public Key StickLeft; + public Key StickRight; + public Key StickButton; + public Key ButtonA; + public Key ButtonB; + public Key ButtonX; + public Key ButtonY; + public Key ButtonPlus; + public Key ButtonR; + public Key ButtonZr; + } +} diff --git a/Ryujinx.Common/Configuration/LoggerModule.cs b/Ryujinx.Common/Configuration/LoggerModule.cs new file mode 100644 index 0000000000..504a81418f --- /dev/null +++ b/Ryujinx.Common/Configuration/LoggerModule.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using System; +using System.IO; + +namespace Ryujinx.Configuration +{ + public static class LoggerModule + { + public static void Initialize() + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; + + ConfigurationState.Instance.Logger.EnableDebug.Event += ReloadEnableDebug; + ConfigurationState.Instance.Logger.EnableStub.Event += ReloadEnableStub; + ConfigurationState.Instance.Logger.EnableInfo.Event += ReloadEnableInfo; + ConfigurationState.Instance.Logger.EnableWarn.Event += ReloadEnableWarning; + ConfigurationState.Instance.Logger.EnableError.Event += ReloadEnableError; + ConfigurationState.Instance.Logger.EnableGuest.Event += ReloadEnableGuest; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Event += ReloadEnableFsAccessLog; + ConfigurationState.Instance.Logger.FilteredClasses.Event += ReloadFilteredClasses; + ConfigurationState.Instance.Logger.EnableFileLog.Event += ReloadFileLogger; + } + + private static void ReloadEnableDebug(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Debug, e.NewValue); + } + + private static void ReloadEnableStub(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Stub, e.NewValue); + } + + private static void ReloadEnableInfo(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Info, e.NewValue); + } + + private static void ReloadEnableWarning(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Warning, e.NewValue); + } + + private static void ReloadEnableError(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Error, e.NewValue); + } + + private static void ReloadEnableGuest(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Guest, e.NewValue); + } + + private static void ReloadEnableFsAccessLog(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.AccessLog, e.NewValue); + } + + private static void ReloadFilteredClasses(object sender, ReactiveEventArgs e) + { + bool noFilter = e.NewValue.Length == 0; + + foreach (var logClass in EnumExtensions.GetValues()) + { + Logger.SetEnable(logClass, noFilter); + } + + foreach (var logClass in e.NewValue) + { + Logger.SetEnable(logClass, true); + } + } + + private static void ReloadFileLogger(object sender, ReactiveEventArgs e) + { + if (e.NewValue) + { + Logger.AddTarget(new AsyncLogTargetWrapper( + new FileLogTarget(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.log"), "file"), + 1000, + AsyncLogTargetOverflowAction.Block + )); + } + else + { + Logger.RemoveTarget("file"); + } + } + + private static void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + Logger.Shutdown(); + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + var exception = e.ExceptionObject as Exception; + + Logger.PrintError(LogClass.Emulation, $"Unhandled exception caught: {exception}"); + + if (e.IsTerminating) + { + Logger.Shutdown(); + } + } + } +} diff --git a/Ryujinx.Common/Configuration/System/Language.cs b/Ryujinx.Common/Configuration/System/Language.cs new file mode 100644 index 0000000000..d3af296ba9 --- /dev/null +++ b/Ryujinx.Common/Configuration/System/Language.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Configuration.System +{ + public enum Language + { + Japanese, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese + } +} diff --git a/Ryujinx/Ui/GuiColumns.cs b/Ryujinx.Common/Configuration/Ui/GuiColumns.cs similarity index 90% rename from Ryujinx/Ui/GuiColumns.cs rename to Ryujinx.Common/Configuration/Ui/GuiColumns.cs index b86a273ea8..2b3524aa82 100644 --- a/Ryujinx/Ui/GuiColumns.cs +++ b/Ryujinx.Common/Configuration/Ui/GuiColumns.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Ui +namespace Ryujinx.Configuration.Ui { public struct GuiColumns { diff --git a/Ryujinx.Common/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs index 10b1d97037..83af97b122 100644 --- a/Ryujinx.Common/Logging/Logger.cs +++ b/Ryujinx.Common/Logging/Logger.cs @@ -37,6 +37,12 @@ namespace Ryujinx.Common.Logging m_LogTargets = new List(); m_Time = Stopwatch.StartNew(); + + // Logger should log to console by default + AddTarget(new AsyncLogTargetWrapper( + new ConsoleLogTarget("console"), + 1000, + AsyncLogTargetOverflowAction.Block)); } public static void RestartTime() @@ -44,6 +50,19 @@ namespace Ryujinx.Common.Logging m_Time.Restart(); } + private static ILogTarget GetTarget(string targetName) + { + foreach (var target in m_LogTargets) + { + if (target.Name.Equals(targetName)) + { + return target; + } + } + + return null; + } + public static void AddTarget(ILogTarget target) { m_LogTargets.Add(target); @@ -51,6 +70,20 @@ namespace Ryujinx.Common.Logging Updated += target.Log; } + public static void RemoveTarget(string target) + { + ILogTarget logTarget = GetTarget(target); + + if (logTarget != null) + { + Updated -= logTarget.Log; + + m_LogTargets.Remove(logTarget); + + logTarget.Dispose(); + } + } + public static void Shutdown() { Updated = null; diff --git a/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs index a805a83b6e..c946b67880 100644 --- a/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs +++ b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs @@ -27,6 +27,8 @@ namespace Ryujinx.Common.Logging private readonly int _overflowTimeout; + string ILogTarget.Name { get => _target.Name; } + public AsyncLogTargetWrapper(ILogTarget target) : this(target, -1, AsyncLogTargetOverflowAction.Block) { } diff --git a/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs index 871076a41a..ff5c6f5acb 100644 --- a/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs @@ -9,6 +9,10 @@ namespace Ryujinx.Common.Logging private readonly ILogFormatter _formatter; + private readonly string _name; + + string ILogTarget.Name { get => _name; } + static ConsoleLogTarget() { _logColors = new ConcurrentDictionary { @@ -19,9 +23,10 @@ namespace Ryujinx.Common.Logging }; } - public ConsoleLogTarget() + public ConsoleLogTarget(string name) { _formatter = new DefaultLogFormatter(); + _name = name; } public void Log(object sender, LogEventArgs args) diff --git a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs index 85dc82497a..4db5f7bce5 100644 --- a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text; namespace Ryujinx.Common.Logging @@ -9,13 +10,17 @@ namespace Ryujinx.Common.Logging private readonly StreamWriter _logWriter; private readonly ILogFormatter _formatter; + private readonly string _name; - public FileLogTarget(string path) - : this(path, FileShare.Read, FileMode.Append) + string ILogTarget.Name { get => _name; } + + public FileLogTarget(string path, string name) + : this(path, name, FileShare.Read, FileMode.Append) { } - public FileLogTarget(string path, FileShare fileShare, FileMode fileMode) + public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode) { + _name = name; _logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare)); _formatter = new DefaultLogFormatter(); } diff --git a/Ryujinx.Common/Logging/Targets/ILogTarget.cs b/Ryujinx.Common/Logging/Targets/ILogTarget.cs index 261c5e64b2..d4d26a936d 100644 --- a/Ryujinx.Common/Logging/Targets/ILogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/ILogTarget.cs @@ -5,5 +5,7 @@ namespace Ryujinx.Common.Logging public interface ILogTarget : IDisposable { void Log(object sender, LogEventArgs args); + + string Name { get; } } } diff --git a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs index 410394aa2d..3729b18d13 100644 --- a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using Utf8Json; namespace Ryujinx.Common.Logging @@ -7,10 +8,14 @@ namespace Ryujinx.Common.Logging { private Stream _stream; private bool _leaveOpen; + private string _name; - public JsonLogTarget(Stream stream) + string ILogTarget.Name { get => _name; } + + public JsonLogTarget(Stream stream, string name) { _stream = stream; + _name = name; } public JsonLogTarget(Stream stream, bool leaveOpen) diff --git a/Ryujinx.Common/ReactiveObject.cs b/Ryujinx.Common/ReactiveObject.cs new file mode 100644 index 0000000000..be30e9b2c5 --- /dev/null +++ b/Ryujinx.Common/ReactiveObject.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading; + +namespace Ryujinx.Common +{ + public class ReactiveObject + { + private ReaderWriterLock _readerWriterLock = new ReaderWriterLock(); + private T _value; + + public event EventHandler> Event; + + public T Value + { + get + { + _readerWriterLock.AcquireReaderLock(Timeout.Infinite); + T value = _value; + _readerWriterLock.ReleaseReaderLock(); + + return value; + } + set + { + _readerWriterLock.AcquireWriterLock(Timeout.Infinite); + + T oldValue = _value; + + _value = value; + + _readerWriterLock.ReleaseWriterLock(); + + if (oldValue == null || !oldValue.Equals(_value)) + { + Event?.Invoke(this, new ReactiveEventArgs(oldValue, value)); + } + } + } + + public static implicit operator T(ReactiveObject obj) + { + return obj.Value; + } + } + + public class ReactiveEventArgs + { + public T OldValue { get; } + public T NewValue { get; } + + public ReactiveEventArgs(T oldValue, T newValue) + { + OldValue = oldValue; + NewValue = newValue; + } + } +} diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj index c777b402cc..7f6fa32323 100644 --- a/Ryujinx.Common/Ryujinx.Common.csproj +++ b/Ryujinx.Common/Ryujinx.Common.csproj @@ -27,6 +27,7 @@ + diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index a1ad2ce4ab..b68ef7bde4 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -759,21 +759,42 @@ namespace Ryujinx.HLE.HOS { if (disposing) { - // Force all threads to exit. - lock (Processes) + KProcess terminationProcess = new KProcess(this); + + KThread terminationThread = new KThread(this); + + terminationThread.Initialize(0, 0, 0, 3, 0, terminationProcess, ThreadType.Kernel, () => { - foreach (KProcess process in Processes.Values) + // Force all threads to exit. + lock (Processes) { - process.StopAllThreads(); + foreach (KProcess process in Processes.Values) + { + process.Terminate(); + } } + + // Exit ourself now! + Scheduler.ExitThread(terminationThread); + Scheduler.GetCurrentThread().Exit(); + Scheduler.RemoveThread(terminationThread); + }); + + terminationThread.Start(); + + // Signal the vsync event to avoid issues of KThread waiting on it. + if (Device.EnableDeviceVsync) + { + Device.VsyncEvent.Set(); } + // This is needed as the IPC Dummy KThread is also counted in the ThreadCounter. + ThreadCounter.Signal(); + // It's only safe to release resources once all threads // have exited. ThreadCounter.Signal(); - //ThreadCounter.Wait(); // FIXME: Uncomment this - // BODY: Right now, guest processes don't exit properly because the logic waits for them to exit. - // BODY: However, this doesn't happen when you close the main window so we need to find a way to make them exit gracefully + ThreadCounter.Wait(); Scheduler.Dispose(); diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs index 88c2e69032..e9dd14b2e6 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs @@ -272,6 +272,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process disposableObj.Dispose(); } + entry.Obj.DecrementReferenceCount(); entry.Obj = null; entry.Next = _nextFreeEntry; diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index 1b16d79a38..c74f6fca1b 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -38,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process public ulong PersonalMmHeapPagesCount { get; private set; } - private ProcessState _state; + public ProcessState State { get; private set; } private object _processLock; private object _threadingLock; @@ -383,7 +383,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process Name = creationInfo.Name; - _state = ProcessState.Created; + State = ProcessState.Created; _creationTimestamp = PerformanceCounter.ElapsedMilliseconds; @@ -579,7 +579,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { lock (_processLock) { - if (_state > ProcessState.CreatedAttached) + if (State > ProcessState.CreatedAttached) { return KernelResult.InvalidState; } @@ -733,8 +733,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process mainThread.SetEntryArguments(0, mainThreadHandle); - ProcessState oldState = _state; - ProcessState newState = _state != ProcessState.Created + ProcessState oldState = State; + ProcessState newState = State != ProcessState.Created ? ProcessState.Attached : ProcessState.Started; @@ -768,9 +768,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process private void SetState(ProcessState newState) { - if (_state != newState) + if (State != newState) { - _state = newState; + State = newState; _signaled = true; Signal(); @@ -820,6 +820,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } } + public void DecrementToZeroWhileTerminatingCurrent() + { + System.ThreadCounter.Signal(); + + while (Interlocked.Decrement(ref _threadCount) != 0) + { + Destroy(); + TerminateCurrentProcess(); + } + + // Nintendo panic here because if it reaches this point, the current thread should be already dead. + // As we handle the death of the thread in the post SVC handler and inside the CPU emulator, we don't panic here. + } + public ulong GetMemoryCapacity() { ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory); @@ -909,12 +923,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process lock (_processLock) { - if (_state >= ProcessState.Started) + if (State >= ProcessState.Started) { - if (_state == ProcessState.Started || - _state == ProcessState.Crashed || - _state == ProcessState.Attached || - _state == ProcessState.DebugSuspended) + if (State == ProcessState.Started || + State == ProcessState.Crashed || + State == ProcessState.Attached || + State == ProcessState.DebugSuspended) { SetState(ProcessState.Exiting); @@ -933,23 +947,98 @@ namespace Ryujinx.HLE.HOS.Kernel.Process if (shallTerminate) { - // UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); + UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); HandleTable.Destroy(); - SignalExitForDebugEvent(); + SignalExitToDebugTerminated(); SignalExit(); } return result; } - private void UnpauseAndTerminateAllThreadsExcept(KThread thread) + public void TerminateCurrentProcess() { - // TODO. + bool shallTerminate = false; + + System.CriticalSection.Enter(); + + lock (_processLock) + { + if (State >= ProcessState.Started) + { + if (State == ProcessState.Started || + State == ProcessState.Attached || + State == ProcessState.DebugSuspended) + { + SetState(ProcessState.Exiting); + + shallTerminate = true; + } + } + } + + System.CriticalSection.Leave(); + + if (shallTerminate) + { + UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); + + HandleTable.Destroy(); + + // NOTE: this is supposed to be called in receiving of the mailbox. + SignalExitToDebugExited(); + SignalExit(); + } } - private void SignalExitForDebugEvent() + private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread) + { + lock (_threadingLock) + { + System.CriticalSection.Enter(); + + foreach (KThread thread in _threads) + { + if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) + { + thread.PrepareForTermination(); + } + } + + System.CriticalSection.Leave(); + } + + KThread blockedThread = null; + + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) + { + thread.IncrementReferenceCount(); + + blockedThread = thread; + break; + } + } + } + + if (blockedThread != null) + { + blockedThread.Terminate(); + blockedThread.DecrementReferenceCount(); + } + } + + private void SignalExitToDebugTerminated() + { + // TODO: Debug events. + } + + private void SignalExitToDebugExited() { // TODO: Debug events. } @@ -976,7 +1065,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process lock (_processLock) { - if (_state != ProcessState.Exited && _signaled) + if (State != ProcessState.Exited && _signaled) { _signaled = false; @@ -999,7 +1088,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { foreach (KThread thread in _threads) { - thread.Context.Running = false; + System.Scheduler.ExitThread(thread); System.Scheduler.CoreManager.Set(thread.HostThread); } diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs index e3a4b375b8..0bf5e5fa3d 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs @@ -1,5 +1,6 @@ using ARMeilleure.State; using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall @@ -29,6 +30,15 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall ExecutionContext context = (ExecutionContext)sender; svcFunc(this, context); + + PostSvcHandler(); + } + + private void PostSvcHandler() + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + currentThread.HandlePostSyscall(); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs index 6525628f26..7961f124d0 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs @@ -17,9 +17,41 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall ExitProcess(); } + public KernelResult TerminateProcess64(int handle) + { + return TerminateProcess(handle); + } + + private KernelResult TerminateProcess(int handle) + { + KProcess process = _process.HandleTable.GetObject(handle); + + KernelResult result; + + if (process != null) + { + if (process == _system.Scheduler.GetCurrentProcess()) + { + result = KernelResult.Success; + process.DecrementToZeroWhileTerminatingCurrent(); + } + else + { + result = process.Terminate(); + process.DecrementReferenceCount(); + } + } + else + { + result = KernelResult.InvalidHandle; + } + + return result; + } + private void ExitProcess() { - _system.Scheduler.GetCurrentProcess().Terminate(); + _system.Scheduler.GetCurrentProcess().TerminateCurrentProcess(); } public KernelResult SignalEvent64(int handle) @@ -184,6 +216,15 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { currentThread.PrintGuestStackTrace(); + // As the process is exiting, this is probably caused by emulation termination. + if (currentThread.Owner.State == ProcessState.Exiting) + { + return; + } + + // TODO: Debug events. + currentThread.Owner.TerminateCurrentProcess(); + throw new GuestBrokeExecutionException(); } else diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs index 9ec0931ae6..1c2121f0de 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs @@ -74,7 +74,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall { 0x72, nameof(SvcHandler.ConnectToPort64) }, { 0x73, nameof(SvcHandler.SetProcessMemoryPermission64) }, { 0x77, nameof(SvcHandler.MapProcessCodeMemory64) }, - { 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64) } + { 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64) }, + { 0x7B, nameof(SvcHandler.TerminateProcess64) } }; _svcTable64 = new Action[0x80]; diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 0232bc1684..e1a49a561a 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -70,7 +70,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public ThreadSchedState SchedFlags { get; private set; } - public bool ShallBeTerminated { get; private set; } + private int _shallBeTerminated; + + public bool ShallBeTerminated { get => _shallBeTerminated != 0; set => _shallBeTerminated = value ? 1 : 0; } public bool SyncCancelled { get; set; } public bool WaitingSync { get; set; } @@ -104,7 +106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading int priority, int defaultCpuCore, KProcess owner, - ThreadType type = ThreadType.User) + ThreadType type = ThreadType.User, + ThreadStart customHostThreadStart = null) { if ((uint)type > 3) { @@ -156,7 +159,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading is64Bits = true; } - HostThread = new Thread(() => ThreadStart(entrypoint)); + HostThread = new Thread(customHostThreadStart == null ? () => ThreadStart(entrypoint) : customHostThreadStart); Context = new ARMeilleure.State.ExecutionContext(); @@ -182,6 +185,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading ThreadUid = System.GetThreadUid(); + HostThread.Name = $"Host Thread (thread id {ThreadUid})"; + _hasBeenInitialized = true; if (owner != null) @@ -300,6 +305,100 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading DecrementReferenceCount(); } + public ThreadSchedState PrepareForTermination() + { + System.CriticalSection.Enter(); + + ThreadSchedState result; + + if (Interlocked.CompareExchange(ref _shallBeTerminated, 1, 0) == 0) + { + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.None) + { + SchedFlags = ThreadSchedState.TerminationPending; + } + else + { + if (_forcePauseFlags != ThreadSchedState.None) + { + _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; + + ThreadSchedState oldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowMask; + + AdjustScheduling(oldSchedFlags); + } + + if (BasePriority >= 0x10) + { + SetPriority(0xF); + } + + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Running) + { + // TODO: GIC distributor stuffs (sgir changes ect) + } + + SignaledObj = null; + ObjSyncResult = KernelResult.ThreadTerminating; + + ReleaseAndResume(); + } + } + + result = SchedFlags; + + System.CriticalSection.Leave(); + + return result & ThreadSchedState.LowMask; + } + + public void Terminate() + { + ThreadSchedState state = PrepareForTermination(); + + if (state != ThreadSchedState.TerminationPending) + { + System.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _); + } + } + + public void HandlePostSyscall() + { + ThreadSchedState state; + + do + { + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.Scheduler.ExitThread(this); + Exit(); + + // As the death of the thread is handled by the CPU emulator, we differ from the official kernel and return here. + break; + } + + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + state = ThreadSchedState.TerminationPending; + } + else + { + if (_forcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + state = ThreadSchedState.Running; + } + + System.CriticalSection.Leave(); + } while (state == ThreadSchedState.TerminationPending); + } + private void ExitImpl() { System.CriticalSection.Enter(); diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs index cc407039bc..b2db08dd38 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -1,9 +1,10 @@ using LibHac; using Ryujinx.HLE.HOS.Ipc; +using System; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { - class IStorage : IpcService + class IStorage : IpcService, IDisposable { private LibHac.Fs.IStorage _baseStorage; @@ -51,5 +52,18 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy return (ResultCode)result.Value; } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _baseStorage?.Dispose(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs index 70783b4386..b48377a47e 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -293,13 +293,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu return NvInternalResult.InvalidInput; } - long result = vmm.Map(map.Address, (long)arguments[index].Offset << 16, - (long)arguments[index].Pages << 16); + long result = vmm.Map( + ((long)arguments[index].MapOffset << 16) + map.Address, + (long)arguments[index].GpuOffset << 16, + (long)arguments[index].Pages << 16); if (result < 0) { Logger.PrintWarning(LogClass.ServiceNv, - $"Page 0x{arguments[index].Offset:x16} size 0x{arguments[index].Pages:x16} not allocated!"); + $"Page 0x{arguments[index].GpuOffset:x16} size 0x{arguments[index].Pages:x16} not allocated!"); return NvInternalResult.InvalidInput; } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs index 0cf324b453..bc149d424b 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs @@ -8,8 +8,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types public ushort Flags; public ushort Kind; public int NvMapHandle; - public int Padding; - public uint Offset; + public uint MapOffset; + public uint GpuOffset; public uint Pages; } } diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs index af9b38815e..b2b3d05260 100644 --- a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -1,11 +1,12 @@ -using Ryujinx.Common; -using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging; using Ryujinx.HLE.Utilities; +using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; +using System.Threading; namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd { @@ -379,13 +380,26 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd } } - try + if (fdsCount != 0) { - System.Net.Sockets.Socket.Select(readEvents, writeEvents, errorEvents, timeout); + try + { + System.Net.Sockets.Socket.Select(readEvents, writeEvents, errorEvents, timeout); + } + catch (SocketException exception) + { + return WriteWinSock2Error(context, (WsaError)exception.ErrorCode); + } } - catch (SocketException exception) + else if (timeout == -1) { - return WriteWinSock2Error(context, (WsaError)exception.ErrorCode); + // FIXME: If we get a timeout of -1 and there is no fds to wait on, this should kill the KProces. (need to check that with re) + throw new InvalidOperationException(); + } + else + { + // FIXME: We should make the KThread sleep but we can't do much about it yet. + Thread.Sleep(timeout); } for (int i = 0; i < fdsCount; i++) diff --git a/Ryujinx.HLE/Input/Hid.cs b/Ryujinx.HLE/Input/Hid.cs index 27e6a30873..5cb7f09de6 100644 --- a/Ryujinx.HLE/Input/Hid.cs +++ b/Ryujinx.HLE/Input/Hid.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Configuration.Hid; using Ryujinx.HLE.HOS; +using System; namespace Ryujinx.HLE.Input { @@ -47,18 +49,31 @@ namespace Ryujinx.HLE.Input _keyboardOffset = HidPosition + HidKeyboardOffset; } - public void InitializePrimaryController(ControllerStatus controllerType) + private static ControllerStatus ConvertControllerTypeToState(ControllerType controllerType) { - ControllerId controllerId = controllerType == ControllerStatus.Handheld ? + switch (controllerType) + { + case ControllerType.Handheld: return ControllerStatus.Handheld; + case ControllerType.NpadLeft: return ControllerStatus.NpadLeft; + case ControllerType.NpadRight: return ControllerStatus.NpadRight; + case ControllerType.NpadPair: return ControllerStatus.NpadPair; + case ControllerType.ProController: return ControllerStatus.ProController; + default: throw new NotImplementedException(); + } + } + + public void InitializePrimaryController(ControllerType controllerType) + { + ControllerId controllerId = controllerType == ControllerType.Handheld ? ControllerId.ControllerHandheld : ControllerId.ControllerPlayer1; - if (controllerType == ControllerStatus.ProController) + if (controllerType == ControllerType.ProController) { PrimaryController = new ProController(_device, NpadColor.Black, NpadColor.Black); } else { - PrimaryController = new NpadController(controllerType, + PrimaryController = new NpadController(ConvertControllerTypeToState(controllerType), _device, (NpadColor.BodyNeonRed, NpadColor.BodyNeonRed), (NpadColor.ButtonsNeonBlue, NpadColor.ButtonsNeonBlue)); @@ -67,11 +82,6 @@ namespace Ryujinx.HLE.Input PrimaryController.Connect(controllerId); } - public void InitializeKeyboard() - { - _device.Memory.FillWithZeros(HidPosition + HidKeyboardOffset, HidKeyboardSize); - } - public ControllerButtons UpdateStickButtons( JoystickPosition leftStick, JoystickPosition rightStick) diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 4a15f616e7..a4d07f6ac1 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,8 +1,12 @@ +using LibHac.FsSystem; using Ryujinx.Audio; +using Ryujinx.Configuration; using Ryujinx.Graphics; using Ryujinx.Graphics.Gal; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services; +using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Input; using System; using System.Threading; @@ -60,6 +64,29 @@ namespace Ryujinx.HLE VsyncEvent = new AutoResetEvent(true); } + public void Initialize() + { + System.State.SetLanguage((SystemLanguage)ConfigurationState.Instance.System.Language.Value); + + EnableDeviceVsync = ConfigurationState.Instance.Graphics.EnableVsync; + + // TODO: Make this reloadable and implement Docking/Undocking logic. + System.State.DockedMode = ConfigurationState.Instance.System.EnableDockedMode; + + if (ConfigurationState.Instance.System.EnableMulticoreScheduling) + { + System.EnableMultiCoreScheduling(); + } + + System.FsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode; + + ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices; + } + public void LoadCart(string exeFsDir, string romFsFile = null) { System.LoadCart(exeFsDir, romFsFile); diff --git a/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/Ryujinx.Tests/Cpu/CpuTestSimd.cs index 30dec59ac2..8f7e206972 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimd.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -2856,7 +2856,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, v0: v0, v1: v1); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } [Test, Pairwise] [Explicit] @@ -2892,7 +2892,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, v0: v0, v1: v1); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } [Test, Pairwise] diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs index 17a2853f19..2d5c82318c 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs @@ -582,7 +582,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } [Test, Pairwise] [Explicit] @@ -666,7 +666,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } #endif } diff --git a/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs b/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs index 0f1b0dac25..1d208d6958 100644 --- a/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs +++ b/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs @@ -564,7 +564,7 @@ namespace Ryujinx.Tests.Cpu SingleOpcode(opcodes, v0: v0, v1: v1); - CompareAgainstUnicorn(fpTolerances: FpTolerances.UpToOneUlpsD); // unsigned + CompareAgainstUnicorn(); } [Test, Pairwise] diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json index 8463081fab..e35a67acd7 100644 --- a/Ryujinx/Config.json +++ b/Ryujinx/Config.json @@ -1,4 +1,5 @@ { + "version": 1, "graphics_shaders_dump_path": "", "logging_enable_debug": false, "logging_enable_stub": true, @@ -7,9 +8,7 @@ "logging_enable_error": true, "logging_enable_guest": true, "logging_enable_fs_access_log": false, - "logging_filtered_classes": [ - - ], + "logging_filtered_classes": [], "enable_file_log": true, "system_language": "AmericanEnglish", "docked_mode": false, @@ -32,9 +31,7 @@ "file_size_column": true, "path_column": true }, - "game_dirs": [ - - ], + "game_dirs": [], "enable_custom_theme": false, "custom_theme_path": "", "enable_keyboard": false, diff --git a/Ryujinx/Configuration/DiscordIntegrationModule.cs b/Ryujinx/Configuration/DiscordIntegrationModule.cs new file mode 100644 index 0000000000..15540a1c82 --- /dev/null +++ b/Ryujinx/Configuration/DiscordIntegrationModule.cs @@ -0,0 +1,92 @@ +using DiscordRPC; +using Ryujinx.Common; +using System; +using System.IO; +using System.Linq; + +namespace Ryujinx.Configuration +{ + static class DiscordIntegrationModule + { + private static DiscordRpcClient DiscordClient; + + private static string LargeDescription = "Ryujinx is a Nintendo Switch emulator."; + + public static RichPresence DiscordPresence { get; private set; } + + public static void Initialize() + { + DiscordPresence = new RichPresence + { + Assets = new Assets + { + LargeImageKey = "ryujinx", + LargeImageText = LargeDescription + }, + Details = "Main Menu", + State = "Idling", + Timestamps = new Timestamps(DateTime.UtcNow) + }; + + ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; + } + + private static void Update(object sender, ReactiveEventArgs e) + { + if (e.OldValue != e.NewValue) + { + // If the integration was active, disable it and unload everything + if (e.OldValue) + { + DiscordClient?.Dispose(); + + DiscordClient = null; + } + + // If we need to activate it and the client isn't active, initialize it + if (e.NewValue && DiscordClient == null) + { + DiscordClient = new DiscordRpcClient("568815339807309834"); + + DiscordClient.Initialize(); + DiscordClient.SetPresence(DiscordPresence); + } + } + } + + public static void SwitchToPlayingState(string titleId, string titleName) + { + if (File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(titleId)) + { + DiscordPresence.Assets.LargeImageKey = titleId; + } + + string state = titleId; + + if (state == null) + { + state = "Ryujinx"; + } + else + { + state = state.ToUpper(); + } + + string details = "Idling"; + + if (titleName != null) + { + details = $"Playing {titleName}"; + } + + DiscordPresence.Details = details; + DiscordPresence.State = state; + DiscordPresence.Assets.LargeImageText = titleName; + DiscordPresence.Assets.SmallImageKey = "ryujinx"; + DiscordPresence.Assets.SmallImageText = LargeDescription; + DiscordPresence.Timestamps = new Timestamps(DateTime.UtcNow); + + DiscordClient?.SetPresence(DiscordPresence); + } + } +} diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 04a9d2c102..63e87a3bae 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -1,6 +1,7 @@ using ARMeilleure.Translation.AOT; using Gtk; using Ryujinx.Common.Logging; +using Ryujinx.Configuration; using Ryujinx.Profiler; using Ryujinx.Ui; using System; @@ -17,9 +18,32 @@ namespace Ryujinx string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; - GLib.ExceptionManager.UnhandledException += Glib_UnhandledException; + GLib.ExceptionManager.UnhandledException += Glib_UnhandledException; + + // Initialize the configuration + ConfigurationState.Initialize(); + + // Initialize the logger system + LoggerModule.Initialize(); + + // Initialize Discord integration + DiscordIntegrationModule.Initialize(); + + string configurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); + + // Now load the configuration as the other subsystems are now registered + if (File.Exists(configurationPath)) + { + ConfigurationFileFormat configurationFileFormat = ConfigurationFileFormat.Load(configurationPath); + ConfigurationState.Instance.Load(configurationFileFormat); + } + else + { + // No configuration, we load the default values and save it on disk + ConfigurationState.Instance.LoadDefault(); + ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath); + } + Profile.Initialize(); @@ -43,25 +67,6 @@ namespace Ryujinx Application.Run(); } - private static void CurrentDomain_ProcessExit(object sender, EventArgs e) - { - Aot.Dispose(); - Logger.Shutdown(); - } - - private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - Exception exception = e.ExceptionObject as Exception; - - Logger.PrintError(LogClass.Emulation, $"Unhandled exception caught: {exception}"); - - if (e.IsTerminating) - { - Aot.Dispose(); - Logger.Shutdown(); - } - } - private static void Glib_UnhandledException(GLib.UnhandledExceptionArgs e) { Exception exception = e.ExceptionObject as Exception; @@ -70,6 +75,7 @@ namespace Ryujinx if (e.IsTerminating) { + Aot.Dispose(); Logger.Shutdown(); } } diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index c54beffe47..e610e8277e 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -43,6 +43,7 @@ + @@ -63,6 +64,7 @@ + @@ -71,7 +73,6 @@ - diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index c23a369294..e1994803a6 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -1,10 +1,12 @@ using OpenTK; using OpenTK.Graphics; using OpenTK.Input; +using Ryujinx.Configuration; using Ryujinx.Graphics.Gal; using Ryujinx.HLE; using Ryujinx.HLE.Input; using Ryujinx.Profiler.UI; +using Ryujinx.Ui; using System; using System.Threading; @@ -29,6 +31,8 @@ namespace Ryujinx.Ui private MouseState? _mouse = null; + private Input.NpadController _primaryController; + private Thread _renderThread; private bool _resizeEvent; @@ -50,6 +54,8 @@ namespace Ryujinx.Ui _device = device; _renderer = renderer; + _primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls); + Location = new Point( (DisplayDevice.Default.Width / 2) - (Width / 2), (DisplayDevice.Default.Height / 2) - (Height / 2)); @@ -162,16 +168,16 @@ namespace Ryujinx.Ui #endif // Normal Input - currentHotkeyButtons = Configuration.Instance.KeyboardControls.GetHotkeyButtons(keyboard); - currentButton = Configuration.Instance.KeyboardControls.GetButtons(keyboard); + currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + currentButton = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); - if (Configuration.Instance.EnableKeyboard) + if (ConfigurationState.Instance.Hid.EnableKeyboard) { - hidKeyboard = Configuration.Instance.KeyboardControls.GetKeysDown(keyboard); + hidKeyboard = KeyboardControls.GetKeysDown(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); } - (leftJoystickDx, leftJoystickDy) = Configuration.Instance.KeyboardControls.GetLeftStick(keyboard); - (rightJoystickDx, rightJoystickDy) = Configuration.Instance.KeyboardControls.GetRightStick(keyboard); + (leftJoystickDx, leftJoystickDy) = KeyboardControls.GetLeftStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); + (rightJoystickDx, rightJoystickDy) = KeyboardControls.GetRightStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard); } if (!hidKeyboard.HasValue) @@ -183,17 +189,17 @@ namespace Ryujinx.Ui }; } - currentButton |= Configuration.Instance.JoystickControls.GetButtons(); + currentButton |= _primaryController.GetButtons(); // Keyboard has priority stick-wise if (leftJoystickDx == 0 && leftJoystickDy == 0) { - (leftJoystickDx, leftJoystickDy) = Configuration.Instance.JoystickControls.GetLeftStick(); + (leftJoystickDx, leftJoystickDy) = _primaryController.GetLeftStick(); } if (rightJoystickDx == 0 && rightJoystickDy == 0) { - (rightJoystickDx, rightJoystickDy) = Configuration.Instance.JoystickControls.GetRightStick(); + (rightJoystickDx, rightJoystickDy) = _primaryController.GetRightStick(); } leftJoystick = new JoystickPosition @@ -269,7 +275,7 @@ namespace Ryujinx.Ui _device.Hid.SetTouchPoints(); } - if (Configuration.Instance.EnableKeyboard && hidKeyboard.HasValue) + if (ConfigurationState.Instance.Hid.EnableKeyboard && hidKeyboard.HasValue) { _device.Hid.WriteKeyboard(hidKeyboard.Value); } diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs new file mode 100644 index 0000000000..f8d1d6815f --- /dev/null +++ b/Ryujinx/Ui/GameTableContextMenu.cs @@ -0,0 +1,75 @@ +using Gtk; +using Ryujinx.HLE.FileSystem; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.Ui +{ + public class GameTableContextMenu : Menu + { + private static ListStore _gameTableStore; + private static TreeIter _rowIter; + +#pragma warning disable CS0649 +#pragma warning disable IDE0044 + [GUI] MenuItem _openSaveDir; +#pragma warning restore CS0649 +#pragma warning restore IDE0044 + + public GameTableContextMenu(ListStore gameTableStore, TreeIter rowIter) : this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, rowIter) { } + + private GameTableContextMenu(Builder builder, ListStore gameTableStore, TreeIter rowIter) : base(builder.GetObject("_contextMenu").Handle) + { + builder.Autoconnect(this); + + _openSaveDir.Activated += OpenSaveDir_Clicked; + + _gameTableStore = gameTableStore; + _rowIter = rowIter; + } + + //Events + private void OpenSaveDir_Clicked(object sender, EventArgs args) + { + string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; + string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); + string saveDir = System.IO.Path.Combine(new VirtualFileSystem().GetNandPath(), "user", "save", "0000000000000000", "00000000000000000000000000000001", titleId, "0"); + + if (!Directory.Exists(saveDir)) + { + MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null) + { + Title = "Ryujinx", + Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"), + Text = $"Could not find save directory for {titleName} [{titleId}]", + SecondaryText = "Would you like to create the directory?", + WindowPosition = WindowPosition.Center + }; + + if (messageDialog.Run() == (int)ResponseType.Yes) + { + Directory.CreateDirectory(saveDir); + } + else + { + messageDialog.Dispose(); + + return; + } + + messageDialog.Dispose(); + } + + Process.Start(new ProcessStartInfo() + { + FileName = saveDir, + UseShellExecute = true, + Verb = "open" + }); + } + } +} diff --git a/Ryujinx/Ui/GameTableContextMenu.glade b/Ryujinx/Ui/GameTableContextMenu.glade new file mode 100644 index 0000000000..2c9e097292 --- /dev/null +++ b/Ryujinx/Ui/GameTableContextMenu.glade @@ -0,0 +1,18 @@ + + + + + + True + False + + + True + False + Open the folder where saves for the application is loaded + Open Save Directory + True + + + + diff --git a/Ryujinx/Ui/NpadKeyboard.cs b/Ryujinx/Ui/KeyboardControls.cs similarity index 74% rename from Ryujinx/Ui/NpadKeyboard.cs rename to Ryujinx/Ui/KeyboardControls.cs index 95fb222183..db9c0cda8c 100644 --- a/Ryujinx/Ui/NpadKeyboard.cs +++ b/Ryujinx/Ui/KeyboardControls.cs @@ -1,118 +1,67 @@ -using OpenTK.Input; +using OpenTK.Input; using Ryujinx.HLE.Input; +using Ryujinx.UI.Input; -namespace Ryujinx.Ui.Input +namespace Ryujinx.Ui { - public struct NpadKeyboardLeft + public static class KeyboardControls { - public Key StickUp; - public Key StickDown; - public Key StickLeft; - public Key StickRight; - public Key StickButton; - public Key DPadUp; - public Key DPadDown; - public Key DPadLeft; - public Key DPadRight; - public Key ButtonMinus; - public Key ButtonL; - public Key ButtonZl; - } - - public struct NpadKeyboardRight - { - public Key StickUp; - public Key StickDown; - public Key StickLeft; - public Key StickRight; - public Key StickButton; - public Key ButtonA; - public Key ButtonB; - public Key ButtonX; - public Key ButtonY; - public Key ButtonPlus; - public Key ButtonR; - public Key ButtonZr; - } - - public struct KeyboardHotkeys - { - public Key ToggleVsync; - } - - public class NpadKeyboard - { - /// - /// Left JoyCon Keyboard Bindings - /// - public NpadKeyboardLeft LeftJoycon { get; set; } - - /// - /// Right JoyCon Keyboard Bindings - /// - public NpadKeyboardRight RightJoycon { get; set; } - - /// - /// Hotkey Keyboard Bindings - /// - public KeyboardHotkeys Hotkeys { get; private set; } - - public ControllerButtons GetButtons(KeyboardState keyboard) + public static ControllerButtons GetButtons(NpadKeyboard npad, KeyboardState keyboard) { ControllerButtons buttons = 0; - if (keyboard[(Key)LeftJoycon.StickButton]) buttons |= ControllerButtons.StickLeft; - if (keyboard[(Key)LeftJoycon.DPadUp]) buttons |= ControllerButtons.DpadUp; - if (keyboard[(Key)LeftJoycon.DPadDown]) buttons |= ControllerButtons.DpadDown; - if (keyboard[(Key)LeftJoycon.DPadLeft]) buttons |= ControllerButtons.DpadLeft; - if (keyboard[(Key)LeftJoycon.DPadRight]) buttons |= ControllerButtons.DPadRight; - if (keyboard[(Key)LeftJoycon.ButtonMinus]) buttons |= ControllerButtons.Minus; - if (keyboard[(Key)LeftJoycon.ButtonL]) buttons |= ControllerButtons.L; - if (keyboard[(Key)LeftJoycon.ButtonZl]) buttons |= ControllerButtons.Zl; + if (keyboard[(Key)npad.LeftJoycon.StickButton]) buttons |= ControllerButtons.StickLeft; + if (keyboard[(Key)npad.LeftJoycon.DPadUp]) buttons |= ControllerButtons.DpadUp; + if (keyboard[(Key)npad.LeftJoycon.DPadDown]) buttons |= ControllerButtons.DpadDown; + if (keyboard[(Key)npad.LeftJoycon.DPadLeft]) buttons |= ControllerButtons.DpadLeft; + if (keyboard[(Key)npad.LeftJoycon.DPadRight]) buttons |= ControllerButtons.DPadRight; + if (keyboard[(Key)npad.LeftJoycon.ButtonMinus]) buttons |= ControllerButtons.Minus; + if (keyboard[(Key)npad.LeftJoycon.ButtonL]) buttons |= ControllerButtons.L; + if (keyboard[(Key)npad.LeftJoycon.ButtonZl]) buttons |= ControllerButtons.Zl; - if (keyboard[(Key)RightJoycon.StickButton]) buttons |= ControllerButtons.StickRight; - if (keyboard[(Key)RightJoycon.ButtonA]) buttons |= ControllerButtons.A; - if (keyboard[(Key)RightJoycon.ButtonB]) buttons |= ControllerButtons.B; - if (keyboard[(Key)RightJoycon.ButtonX]) buttons |= ControllerButtons.X; - if (keyboard[(Key)RightJoycon.ButtonY]) buttons |= ControllerButtons.Y; - if (keyboard[(Key)RightJoycon.ButtonPlus]) buttons |= ControllerButtons.Plus; - if (keyboard[(Key)RightJoycon.ButtonR]) buttons |= ControllerButtons.R; - if (keyboard[(Key)RightJoycon.ButtonZr]) buttons |= ControllerButtons.Zr; + if (keyboard[(Key)npad.RightJoycon.StickButton]) buttons |= ControllerButtons.StickRight; + if (keyboard[(Key)npad.RightJoycon.ButtonA]) buttons |= ControllerButtons.A; + if (keyboard[(Key)npad.RightJoycon.ButtonB]) buttons |= ControllerButtons.B; + if (keyboard[(Key)npad.RightJoycon.ButtonX]) buttons |= ControllerButtons.X; + if (keyboard[(Key)npad.RightJoycon.ButtonY]) buttons |= ControllerButtons.Y; + if (keyboard[(Key)npad.RightJoycon.ButtonPlus]) buttons |= ControllerButtons.Plus; + if (keyboard[(Key)npad.RightJoycon.ButtonR]) buttons |= ControllerButtons.R; + if (keyboard[(Key)npad.RightJoycon.ButtonZr]) buttons |= ControllerButtons.Zr; return buttons; } - public (short, short) GetLeftStick(KeyboardState keyboard) + public static (short, short) GetLeftStick(NpadKeyboard npad, KeyboardState keyboard) { short dx = 0; short dy = 0; - if (keyboard[(Key)LeftJoycon.StickUp]) dy = short.MaxValue; - if (keyboard[(Key)LeftJoycon.StickDown]) dy = -short.MaxValue; - if (keyboard[(Key)LeftJoycon.StickLeft]) dx = -short.MaxValue; - if (keyboard[(Key)LeftJoycon.StickRight]) dx = short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickUp]) dy = short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickDown]) dy = -short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickLeft]) dx = -short.MaxValue; + if (keyboard[(Key)npad.LeftJoycon.StickRight]) dx = short.MaxValue; return (dx, dy); } - public (short, short) GetRightStick(KeyboardState keyboard) + public static (short, short) GetRightStick(NpadKeyboard npad, KeyboardState keyboard) { short dx = 0; short dy = 0; - if (keyboard[(Key)RightJoycon.StickUp]) dy = short.MaxValue; - if (keyboard[(Key)RightJoycon.StickDown]) dy = -short.MaxValue; - if (keyboard[(Key)RightJoycon.StickLeft]) dx = -short.MaxValue; - if (keyboard[(Key)RightJoycon.StickRight]) dx = short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickUp]) dy = short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickDown]) dy = -short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickLeft]) dx = -short.MaxValue; + if (keyboard[(Key)npad.RightJoycon.StickRight]) dx = short.MaxValue; return (dx, dy); } - public HotkeyButtons GetHotkeyButtons(KeyboardState keyboard) + public static HotkeyButtons GetHotkeyButtons(NpadKeyboard npad, KeyboardState keyboard) { HotkeyButtons buttons = 0; - if (keyboard[(Key)Hotkeys.ToggleVsync]) buttons |= HotkeyButtons.ToggleVSync; + if (keyboard[(Key)npad.Hotkeys.ToggleVsync]) buttons |= HotkeyButtons.ToggleVSync; return buttons; } @@ -267,7 +216,7 @@ namespace Ryujinx.Ui.Input new KeyMappingEntry { TargetKey = Key.NumLock, Target = 10 }, }; - public HLE.Input.Keyboard GetKeysDown(KeyboardState keyboard) + public static HLE.Input.Keyboard GetKeysDown(NpadKeyboard npad, KeyboardState keyboard) { HLE.Input.Keyboard hidKeyboard = new HLE.Input.Keyboard { diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 1248112290..ba813b3df2 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -1,21 +1,20 @@ using ARMeilleure.Translation.AOT; -using DiscordRPC; using Gtk; using JsonPrettyPrinterPlus; using Ryujinx.Audio; using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Gal.OpenGL; +using Ryujinx.Configuration; using Ryujinx.Graphics.Gal; +using Ryujinx.Graphics.Gal.OpenGL; using Ryujinx.HLE.FileSystem; using Ryujinx.Profiler; using System; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; using Utf8Json; using Utf8Json.Resolvers; @@ -39,24 +38,8 @@ namespace Ryujinx.Ui private static bool _gameLoaded; private static bool _ending; - private static TreeViewColumn _favColumn; - private static TreeViewColumn _appColumn; - private static TreeViewColumn _devColumn; - private static TreeViewColumn _versionColumn; - private static TreeViewColumn _timePlayedColumn; - private static TreeViewColumn _lastPlayedColumn; - private static TreeViewColumn _fileExtColumn; - private static TreeViewColumn _fileSizeColumn; - private static TreeViewColumn _pathColumn; - private static TreeView _treeView; - public static bool DiscordIntegrationEnabled { get; set; } - - public static DiscordRpcClient DiscordClient; - - public static RichPresence DiscordPresence; - #pragma warning disable CS0649 #pragma warning disable IDE0044 [GUI] Window _mainWin; @@ -73,6 +56,7 @@ namespace Ryujinx.Ui [GUI] CheckMenuItem _fileSizeToggle; [GUI] CheckMenuItem _pathToggle; [GUI] TreeView _gameTable; + [GUI] TreeSelection _gameTableSelection; [GUI] Label _progressLabel; [GUI] LevelBar _progressBar; #pragma warning restore CS0649 @@ -88,64 +72,45 @@ namespace Ryujinx.Ui ApplicationLibrary.ApplicationAdded += Application_Added; + _gameTable.ButtonReleaseEvent += Row_Clicked; + _renderer = new OglRenderer(); _audioOut = InitializeAudioEngine(); - _device = new HLE.Switch(_renderer, _audioOut); + // TODO: Initialization and dispose of HLE.Switch when starting/stoping emulation. + _device = InitializeSwitchInstance(); _treeView = _gameTable; - Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); - Configuration.InitialConfigure(_device); - ApplyTheme(); - if (DiscordIntegrationEnabled) - { - DiscordClient = new DiscordRpcClient("568815339807309834"); - DiscordPresence = new RichPresence - { - Assets = new Assets - { - LargeImageKey = "ryujinx", - LargeImageText = "Ryujinx is an emulator for the Nintendo Switch" - }, - Details = "Main Menu", - State = "Idling", - Timestamps = new Timestamps(DateTime.UtcNow) - }; - - DiscordClient.Initialize(); - DiscordClient.SetPresence(DiscordPresence); - } - _mainWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); _stopEmulation.Sensitive = false; - if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _favToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _iconToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _appToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _developerToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn) { _versionToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _fileExtToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _fileSizeToggle.Active = true; } - if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _pathToggle.Active = true; } + if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _iconToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _appToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _developerToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _versionToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _timePlayedToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _lastPlayedToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _fileExtToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _fileSizeToggle.Active = true; + if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _pathToggle.Active = true; _gameTable.Model = _tableStore = new ListStore( - typeof(bool), - typeof(Gdk.Pixbuf), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), - typeof(string), + typeof(bool), + typeof(Gdk.Pixbuf), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), typeof(string)); - + _tableStore.SetSortFunc(5, TimePlayedSort); _tableStore.SetSortFunc(6, LastPlayedSort); _tableStore.SetSortFunc(8, FileSizeSort); @@ -159,22 +124,22 @@ namespace Ryujinx.Ui internal static void ApplyTheme() { - if (!SwitchSettings.SwitchConfig.EnableCustomTheme) + if (!ConfigurationState.Instance.Ui.EnableCustomTheme) { return; } - if (File.Exists(SwitchSettings.SwitchConfig.CustomThemePath) && (System.IO.Path.GetExtension(SwitchSettings.SwitchConfig.CustomThemePath) == ".css")) + if (File.Exists(ConfigurationState.Instance.Ui.CustomThemePath) && (System.IO.Path.GetExtension(ConfigurationState.Instance.Ui.CustomThemePath) == ".css")) { CssProvider cssProvider = new CssProvider(); - cssProvider.LoadFromPath(SwitchSettings.SwitchConfig.CustomThemePath); + cssProvider.LoadFromPath(ConfigurationState.Instance.Ui.CustomThemePath); StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); } else { - Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{SwitchSettings.SwitchConfig.CustomThemePath}\"."); + Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\"."); } } @@ -188,39 +153,38 @@ namespace Ryujinx.Ui CellRendererToggle favToggle = new CellRendererToggle(); favToggle.Toggled += FavToggle_Toggled; - if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _gameTable.AppendColumn("Fav", favToggle, "active", 0); } - if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); } - if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); } - if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); } - if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); } - if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); } - if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); } - if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); } - if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); } - if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); } + if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _gameTable.AppendColumn("Fav", favToggle, "active", 0); + if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); + if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); + if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); + if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); + if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); + if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); + if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); + if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); + if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); foreach (TreeViewColumn column in _gameTable.Columns) { - if (column.Title == "Fav") { _favColumn = column; } - else if (column.Title == "Application") { _appColumn = column; } - else if (column.Title == "Developer") { _devColumn = column; } - else if (column.Title == "Version") { _versionColumn = column; } - else if (column.Title == "Time Played") { _timePlayedColumn = column; } - else if (column.Title == "Last Played") { _lastPlayedColumn = column; } - else if (column.Title == "File Ext") { _fileExtColumn = column; } - else if (column.Title == "File Size") { _fileSizeColumn = column; } - else if (column.Title == "Path") { _pathColumn = column; } + if (column.Title == "Fav" && ConfigurationState.Instance.Ui.GuiColumns.FavColumn) column.SortColumnId = 0; + else if (column.Title == "Application" && ConfigurationState.Instance.Ui.GuiColumns.AppColumn) column.SortColumnId = 2; + else if (column.Title == "Developer" && ConfigurationState.Instance.Ui.GuiColumns.DevColumn) column.SortColumnId = 3; + else if (column.Title == "Version" && ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) column.SortColumnId = 4; + else if (column.Title == "Time Played" && ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) column.SortColumnId = 5; + else if (column.Title == "Last Played" && ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) column.SortColumnId = 6; + else if (column.Title == "File Ext" && ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) column.SortColumnId = 7; + else if (column.Title == "File Size" && ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) column.SortColumnId = 8; + else if (column.Title == "Path" && ConfigurationState.Instance.Ui.GuiColumns.PathColumn) column.SortColumnId = 9; } + } - if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _favColumn.SortColumnId = 0; } - if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _appColumn.SortColumnId = 2; } - if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _devColumn.SortColumnId = 3; } - if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _versionColumn.SortColumnId = 4; } - if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedColumn.SortColumnId = 5; } - if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedColumn.SortColumnId = 6; } - if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _fileExtColumn.SortColumnId = 7; } - if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _fileSizeColumn.SortColumnId = 8; } - if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _pathColumn.SortColumnId = 9; } + private HLE.Switch InitializeSwitchInstance() + { + HLE.Switch instance = new HLE.Switch(_renderer, _audioOut); + + instance.Initialize(); + + return instance; } internal static async Task UpdateGameTable() @@ -234,7 +198,7 @@ namespace Ryujinx.Ui _tableStore.Clear(); - await Task.Run(() => ApplicationLibrary.LoadApplications(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage)); + await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage)); _updatingGameTable = false; } @@ -249,6 +213,9 @@ namespace Ryujinx.Ui { Logger.RestartTime(); + // TODO: Move this somewhere else + reloadable? + GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + if (Directory.Exists(path)) { string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); @@ -314,40 +281,7 @@ namespace Ryujinx.Ui _gameLoaded = true; _stopEmulation.Sensitive = true; - if (DiscordIntegrationEnabled) - { - if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleId)) - { - DiscordPresence.Assets.LargeImageKey = _device.System.TitleId; - } - - string state = _device.System.TitleId; - - if (state == null) - { - state = "Ryujinx"; - } - else - { - state = state.ToUpper(); - } - - string details = "Idling"; - - if (_device.System.TitleName != null) - { - details = $"Playing {_device.System.TitleName}"; - } - - DiscordPresence.Details = details; - DiscordPresence.State = state; - DiscordPresence.Assets.LargeImageText = _device.System.TitleName; - DiscordPresence.Assets.SmallImageKey = "ryujinx"; - DiscordPresence.Assets.SmallImageText = "Ryujinx is an emulator for the Nintendo Switch"; - DiscordPresence.Timestamps = new Timestamps(DateTime.UtcNow); - - DiscordClient.SetPresence(DiscordPresence); - } + DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleId, _device.System.TitleName); string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui"); string metadataFile = System.IO.Path.Combine(metadataFolder, "metadata.json"); @@ -385,8 +319,8 @@ namespace Ryujinx.Ui private static void CreateGameWindow() { - Configuration.ConfigureHid(_device, SwitchSettings.SwitchConfig); - + _device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType); + using (_screen = new GlScreen(_device, _renderer)) { _screen.MainLoop(); @@ -443,10 +377,9 @@ namespace Ryujinx.Ui } Profile.FinishProfiling(); - Aot.Dispose(); _device.Dispose(); _audioOut.Dispose(); - DiscordClient?.Dispose(); + Aot.Dispose(); Logger.Shutdown(); Environment.Exit(0); } @@ -472,24 +405,24 @@ namespace Ryujinx.Ui } //Events - private void Application_Added(object sender, ApplicationAddedEventArgs e) + private void Application_Added(object sender, ApplicationAddedEventArgs args) { Application.Invoke(delegate { _tableStore.AppendValues( - e.AppData.Favorite, - new Gdk.Pixbuf(e.AppData.Icon, 75, 75), - $"{e.AppData.TitleName}\n{e.AppData.TitleId.ToUpper()}", - e.AppData.Developer, - e.AppData.Version, - e.AppData.TimePlayed, - e.AppData.LastPlayed, - e.AppData.FileExtension, - e.AppData.FileSize, - e.AppData.Path); + args.AppData.Favorite, + new Gdk.Pixbuf(args.AppData.Icon, 75, 75), + $"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}", + args.AppData.Developer, + args.AppData.Version, + args.AppData.TimePlayed, + args.AppData.LastPlayed, + args.AppData.FileExtension, + args.AppData.FileSize, + args.AppData.Path); - _progressLabel.Text = $"{e.NumAppsLoaded}/{e.NumAppsFound} Games Loaded"; - _progressBar.Value = (float)e.NumAppsLoaded / e.NumAppsFound; + _progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded"; + _progressBar.Value = (float)args.NumAppsLoaded / args.NumAppsFound; }); } @@ -528,12 +461,25 @@ namespace Ryujinx.Ui private void Row_Activated(object sender, RowActivatedArgs args) { - _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path.ToString())); + _gameTableSelection.GetSelected(out TreeIter treeIter); string path = (string)_tableStore.GetValue(treeIter, 9); LoadApplication(path); } + private void Row_Clicked(object sender, ButtonReleaseEventArgs args) + { + if (args.Event.Button != 3) return; + + _gameTableSelection.GetSelected(out TreeIter treeIter); + + if (treeIter.UserData == IntPtr.Zero) return; + + GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter); + contextMenu.ShowAll(); + contextMenu.PopupAtPointer(null); + } + private void Load_Application_File(object sender, EventArgs args) { FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept); @@ -609,7 +555,7 @@ namespace Ryujinx.Ui private void Settings_Pressed(object sender, EventArgs args) { - SwitchSettings settingsWin = new SwitchSettings(_device); + SwitchSettings settingsWin = new SwitchSettings(); settingsWin.Show(); } @@ -635,121 +581,81 @@ namespace Ryujinx.Ui private void Fav_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.FavColumn = _favToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.FavColumn.Value = _favToggle.Active; + SaveConfig(); UpdateColumns(); } private void Icon_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.IconColumn = _iconToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.IconColumn.Value = _iconToggle.Active; + SaveConfig(); UpdateColumns(); } private void Title_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.AppColumn = _appToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.AppColumn.Value = _appToggle.Active; + SaveConfig(); UpdateColumns(); } private void Developer_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.DevColumn = _developerToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.DevColumn.Value = _developerToggle.Active; + SaveConfig(); UpdateColumns(); } private void Version_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.VersionColumn = _versionToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.VersionColumn.Value = _versionToggle.Active; + SaveConfig(); UpdateColumns(); } private void TimePlayed_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.TimePlayedColumn = _timePlayedToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active; + SaveConfig(); UpdateColumns(); } private void LastPlayed_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.LastPlayedColumn = _lastPlayedToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active; + SaveConfig(); UpdateColumns(); } private void FileExt_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.FileExtColumn = _fileExtToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active; + SaveConfig(); UpdateColumns(); } private void FileSize_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.FileSizeColumn = _fileSizeToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active; + SaveConfig(); UpdateColumns(); } private void Path_Toggled(object sender, EventArgs args) { - GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns; - - updatedColumns.PathColumn = _pathToggle.Active; - SwitchSettings.SwitchConfig.GuiColumns = updatedColumns; - - Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + ConfigurationState.Instance.Ui.GuiColumns.PathColumn.Value = _pathToggle.Active; + SaveConfig(); UpdateColumns(); } @@ -867,5 +773,10 @@ namespace Ryujinx.Ui return 0; } } + + public static void SaveConfig() + { + ConfigurationState.Instance.ToFileFormat().SaveConfig(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); + } } } diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade index 6d66dc2007..fcf91bc4c5 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -330,7 +330,7 @@ True - + diff --git a/Ryujinx/Ui/NpadController.cs b/Ryujinx/Ui/NpadController.cs index f72c407551..67961b4914 100644 --- a/Ryujinx/Ui/NpadController.cs +++ b/Ryujinx/Ui/NpadController.cs @@ -1,160 +1,57 @@ using OpenTK; using OpenTK.Input; +using Ryujinx.Common.Configuration.Hid; using Ryujinx.HLE.Input; using System; +using InnerNpadController = Ryujinx.Common.Configuration.Hid.NpadController; + namespace Ryujinx.Ui.Input { - public enum ControllerInputId - { - Button0, - Button1, - Button2, - Button3, - Button4, - Button5, - Button6, - Button7, - Button8, - Button9, - Button10, - Button11, - Button12, - Button13, - Button14, - Button15, - Button16, - Button17, - Button18, - Button19, - Button20, - Axis0, - Axis1, - Axis2, - Axis3, - Axis4, - Axis5, - Hat0Up, - Hat0Down, - Hat0Left, - Hat0Right, - Hat1Up, - Hat1Down, - Hat1Left, - Hat1Right, - Hat2Up, - Hat2Down, - Hat2Left, - Hat2Right, - } - - public struct NpadControllerLeft - { - public ControllerInputId Stick; - public ControllerInputId StickButton; - public ControllerInputId ButtonMinus; - public ControllerInputId ButtonL; - public ControllerInputId ButtonZl; - public ControllerInputId DPadUp; - public ControllerInputId DPadDown; - public ControllerInputId DPadLeft; - public ControllerInputId DPadRight; - } - - public struct NpadControllerRight - { - public ControllerInputId Stick; - public ControllerInputId StickButton; - public ControllerInputId ButtonA; - public ControllerInputId ButtonB; - public ControllerInputId ButtonX; - public ControllerInputId ButtonY; - public ControllerInputId ButtonPlus; - public ControllerInputId ButtonR; - public ControllerInputId ButtonZr; - } - public class NpadController { - /// - /// Enables or disables controller support - /// - public bool Enabled { get; private set; } + private InnerNpadController _inner; - /// - /// Controller Device Index - /// - public int Index { get; private set; } - - /// - /// Controller Analog Stick Deadzone - /// - public float Deadzone { get; private set; } - - /// - /// Controller Trigger Threshold - /// - public float TriggerThreshold { get; private set; } - - /// - /// Left JoyCon Controller Bindings - /// - public NpadControllerLeft LeftJoycon { get; private set; } - - /// - /// Right JoyCon Controller Bindings - /// - public NpadControllerRight RightJoycon { get; private set; } - - public NpadController( - bool enabled, - int index, - float deadzone, - float triggerThreshold, - NpadControllerLeft leftJoycon, - NpadControllerRight rightJoycon) + // NOTE: This should be initialized AFTER GTK for compat reasons with OpenTK SDL2 backend and GTK on Linux. + // BODY: Usage of Joystick.GetState must be defer to after GTK full initialization. Otherwise, GTK will segfault because SDL2 was already init *sighs* + public NpadController(InnerNpadController inner) { - Enabled = enabled; - Index = index; - Deadzone = deadzone; - TriggerThreshold = triggerThreshold; - LeftJoycon = leftJoycon; - RightJoycon = rightJoycon; + _inner = inner; } - public void SetEnabled(bool enabled) + private bool IsEnabled() { - Enabled = enabled; + return _inner.Enabled && Joystick.GetState(_inner.Index).IsConnected; } public ControllerButtons GetButtons() { - if (!Enabled) + if (!IsEnabled()) { return 0; } - JoystickState joystickState = Joystick.GetState(Index); + JoystickState joystickState = Joystick.GetState(_inner.Index); ControllerButtons buttons = 0; - if (IsActivated(joystickState, LeftJoycon.DPadUp)) buttons |= ControllerButtons.DpadUp; - if (IsActivated(joystickState, LeftJoycon.DPadDown)) buttons |= ControllerButtons.DpadDown; - if (IsActivated(joystickState, LeftJoycon.DPadLeft)) buttons |= ControllerButtons.DpadLeft; - if (IsActivated(joystickState, LeftJoycon.DPadRight)) buttons |= ControllerButtons.DPadRight; - if (IsActivated(joystickState, LeftJoycon.StickButton)) buttons |= ControllerButtons.StickLeft; - if (IsActivated(joystickState, LeftJoycon.ButtonMinus)) buttons |= ControllerButtons.Minus; - if (IsActivated(joystickState, LeftJoycon.ButtonL)) buttons |= ControllerButtons.L; - if (IsActivated(joystickState, LeftJoycon.ButtonZl)) buttons |= ControllerButtons.Zl; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadUp)) buttons |= ControllerButtons.DpadUp; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadDown)) buttons |= ControllerButtons.DpadDown; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadLeft)) buttons |= ControllerButtons.DpadLeft; + if (IsActivated(joystickState, _inner.LeftJoycon.DPadRight)) buttons |= ControllerButtons.DPadRight; + if (IsActivated(joystickState, _inner.LeftJoycon.StickButton)) buttons |= ControllerButtons.StickLeft; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonMinus)) buttons |= ControllerButtons.Minus; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonL)) buttons |= ControllerButtons.L; + if (IsActivated(joystickState, _inner.LeftJoycon.ButtonZl)) buttons |= ControllerButtons.Zl; - if (IsActivated(joystickState, RightJoycon.ButtonA)) buttons |= ControllerButtons.A; - if (IsActivated(joystickState, RightJoycon.ButtonB)) buttons |= ControllerButtons.B; - if (IsActivated(joystickState, RightJoycon.ButtonX)) buttons |= ControllerButtons.X; - if (IsActivated(joystickState, RightJoycon.ButtonY)) buttons |= ControllerButtons.Y; - if (IsActivated(joystickState, RightJoycon.StickButton)) buttons |= ControllerButtons.StickRight; - if (IsActivated(joystickState, RightJoycon.ButtonPlus)) buttons |= ControllerButtons.Plus; - if (IsActivated(joystickState, RightJoycon.ButtonR)) buttons |= ControllerButtons.R; - if (IsActivated(joystickState, RightJoycon.ButtonZr)) buttons |= ControllerButtons.Zr; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonA)) buttons |= ControllerButtons.A; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonB)) buttons |= ControllerButtons.B; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonX)) buttons |= ControllerButtons.X; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonY)) buttons |= ControllerButtons.Y; + if (IsActivated(joystickState, _inner.RightJoycon.StickButton)) buttons |= ControllerButtons.StickRight; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonPlus)) buttons |= ControllerButtons.Plus; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonR)) buttons |= ControllerButtons.R; + if (IsActivated(joystickState, _inner.RightJoycon.ButtonZr)) buttons |= ControllerButtons.Zr; return buttons; } @@ -169,7 +66,7 @@ namespace Ryujinx.Ui.Input { int axis = controllerInputId - ControllerInputId.Axis0; - return joystickState.GetAxis(axis) > TriggerThreshold; + return joystickState.GetAxis(axis) > _inner.TriggerThreshold; } else if (controllerInputId <= ControllerInputId.Hat2Right) { @@ -190,22 +87,22 @@ namespace Ryujinx.Ui.Input public (short, short) GetLeftStick() { - if (!Enabled) + if (!IsEnabled()) { return (0, 0); } - return GetStick(LeftJoycon.Stick); + return GetStick(_inner.LeftJoycon.Stick); } public (short, short) GetRightStick() { - if (!Enabled) + if (!IsEnabled()) { return (0, 0); } - return GetStick(RightJoycon.Stick); + return GetStick(_inner.RightJoycon.Stick); } private (short, short) GetStick(ControllerInputId stickInputId) @@ -215,7 +112,7 @@ namespace Ryujinx.Ui.Input return (0, 0); } - JoystickState jsState = Joystick.GetState(Index); + JoystickState jsState = Joystick.GetState(_inner.Index); int xAxis = stickInputId - ControllerInputId.Axis0; @@ -227,8 +124,8 @@ namespace Ryujinx.Ui.Input private (short, short) ApplyDeadzone(Vector2 axis) { - return (ClampAxis(MathF.Abs(axis.X) > Deadzone ? axis.X : 0f), - ClampAxis(MathF.Abs(axis.Y) > Deadzone ? axis.Y : 0f)); + return (ClampAxis(MathF.Abs(axis.X) > _inner.Deadzone ? axis.X : 0f), + ClampAxis(MathF.Abs(axis.Y) > _inner.Deadzone ? axis.Y : 0f)); } private static short ClampAxis(float value) diff --git a/Ryujinx/Ui/SwitchSettings.cs b/Ryujinx/Ui/SwitchSettings.cs index 955c6b0b64..5c56cf7ea1 100644 --- a/Ryujinx/Ui/SwitchSettings.cs +++ b/Ryujinx/Ui/SwitchSettings.cs @@ -1,12 +1,12 @@ using Gtk; -using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Input; -using Ryujinx.Ui.Input; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Ryujinx.Configuration; +using Ryujinx.Configuration.System; +using Ryujinx.Configuration.Hid; using GUI = Gtk.Builder.ObjectAttribute; @@ -14,10 +14,6 @@ namespace Ryujinx.Ui { public class SwitchSettings : Window { - internal static Configuration SwitchConfig { get; set; } - - private readonly HLE.Switch _device; - private static ListStore _gameDirsBoxStore; private static bool _listeningForKeypress; @@ -83,16 +79,12 @@ namespace Ryujinx.Ui #pragma warning restore CS0649 #pragma warning restore IDE0044 - public static void ConfigureSettings(Configuration instance) { SwitchConfig = instance; } + public SwitchSettings() : this(new Builder("Ryujinx.Ui.SwitchSettings.glade")) { } - public SwitchSettings(HLE.Switch device) : this(new Builder("Ryujinx.Ui.SwitchSettings.glade"), device) { } - - private SwitchSettings(Builder builder, HLE.Switch device) : base(builder.GetObject("_settingsWin").Handle) + private SwitchSettings(Builder builder) : base(builder.GetObject("_settingsWin").Handle) { builder.Autoconnect(this); - _device = device; - _settingsWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"); _controller1Image.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500); @@ -124,60 +116,123 @@ namespace Ryujinx.Ui _controller1Type.Changed += (sender, args) => Controller_Changed(sender, args, _controller1Type.ActiveId, _controller1Image); //Setup Currents - if (SwitchConfig.EnableFileLog) _fileLogToggle.Click(); - if (SwitchConfig.LoggingEnableError) _errorLogToggle.Click(); - if (SwitchConfig.LoggingEnableWarn) _warningLogToggle.Click(); - if (SwitchConfig.LoggingEnableInfo) _infoLogToggle.Click(); - if (SwitchConfig.LoggingEnableStub) _stubLogToggle.Click(); - if (SwitchConfig.LoggingEnableDebug) _debugLogToggle.Click(); - if (SwitchConfig.LoggingEnableGuest) _guestLogToggle.Click(); - if (SwitchConfig.LoggingEnableFsAccessLog) _fsAccessLogToggle.Click(); - if (SwitchConfig.DockedMode) _dockedModeToggle.Click(); - if (SwitchConfig.EnableDiscordIntegration) _discordToggle.Click(); - if (SwitchConfig.EnableVsync) _vSyncToggle.Click(); - if (SwitchConfig.EnableMulticoreScheduling) _multiSchedToggle.Click(); - if (SwitchConfig.EnableFsIntegrityChecks) _fsicToggle.Click(); - if (SwitchConfig.IgnoreMissingServices) _ignoreToggle.Click(); - if (SwitchConfig.EnableKeyboard) _directKeyboardAccess.Click(); - if (SwitchConfig.EnableCustomTheme) _custThemeToggle.Click(); + if (ConfigurationState.Instance.Logger.EnableFileLog) + { + _fileLogToggle.Click(); + } - _systemLanguageSelect.SetActiveId(SwitchConfig.SystemLanguage.ToString()); - _controller1Type .SetActiveId(SwitchConfig.ControllerType.ToString()); + if (ConfigurationState.Instance.Logger.EnableError) + { + _errorLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableWarn) + { + _warningLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableInfo) + { + _infoLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableStub) + { + _stubLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableDebug) + { + _debugLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableGuest) + { + _guestLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableFsAccessLog) + { + _fsAccessLogToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableDockedMode) + { + _dockedModeToggle.Click(); + } + + if (ConfigurationState.Instance.EnableDiscordIntegration) + { + _discordToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableVsync) + { + _vSyncToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableMulticoreScheduling) + { + _multiSchedToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableFsIntegrityChecks) + { + _fsicToggle.Click(); + } + + if (ConfigurationState.Instance.System.IgnoreMissingServices) + { + _ignoreToggle.Click(); + } + + if (ConfigurationState.Instance.Hid.EnableKeyboard) + { + _directKeyboardAccess.Click(); + } + + if (ConfigurationState.Instance.Ui.EnableCustomTheme) + { + _custThemeToggle.Click(); + } + + _systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString()); + _controller1Type .SetActiveId(ConfigurationState.Instance.Hid.ControllerType.Value.ToString()); Controller_Changed(null, null, _controller1Type.ActiveId, _controller1Image); - _lStickUp1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickUp.ToString(); - _lStickDown1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickDown.ToString(); - _lStickLeft1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickLeft.ToString(); - _lStickRight1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickRight.ToString(); - _lStickButton1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickButton.ToString(); - _dpadUp1.Label = SwitchConfig.KeyboardControls.LeftJoycon.DPadUp.ToString(); - _dpadDown1.Label = SwitchConfig.KeyboardControls.LeftJoycon.DPadDown.ToString(); - _dpadLeft1.Label = SwitchConfig.KeyboardControls.LeftJoycon.DPadLeft.ToString(); - _dpadRight1.Label = SwitchConfig.KeyboardControls.LeftJoycon.DPadRight.ToString(); - _minus1.Label = SwitchConfig.KeyboardControls.LeftJoycon.ButtonMinus.ToString(); - _l1.Label = SwitchConfig.KeyboardControls.LeftJoycon.ButtonL.ToString(); - _zL1.Label = SwitchConfig.KeyboardControls.LeftJoycon.ButtonZl.ToString(); - _rStickUp1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickUp.ToString(); - _rStickDown1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickDown.ToString(); - _rStickLeft1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickLeft.ToString(); - _rStickRight1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickRight.ToString(); - _rStickButton1.Label = SwitchConfig.KeyboardControls.RightJoycon.StickButton.ToString(); - _a1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonA.ToString(); - _b1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonB.ToString(); - _x1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonX.ToString(); - _y1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonY.ToString(); - _plus1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonPlus.ToString(); - _r1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonR.ToString(); - _zR1.Label = SwitchConfig.KeyboardControls.RightJoycon.ButtonZr.ToString(); + _lStickUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickUp.ToString(); + _lStickDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickDown.ToString(); + _lStickLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickLeft.ToString(); + _lStickRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickRight.ToString(); + _lStickButton1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.StickButton.ToString(); + _dpadUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadUp.ToString(); + _dpadDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadDown.ToString(); + _dpadLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadLeft.ToString(); + _dpadRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.DPadRight.ToString(); + _minus1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonMinus.ToString(); + _l1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonL.ToString(); + _zL1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon.ButtonZl.ToString(); + _rStickUp1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickUp.ToString(); + _rStickDown1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickDown.ToString(); + _rStickLeft1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickLeft.ToString(); + _rStickRight1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickRight.ToString(); + _rStickButton1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.StickButton.ToString(); + _a1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonA.ToString(); + _b1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonB.ToString(); + _x1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonX.ToString(); + _y1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonY.ToString(); + _plus1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonPlus.ToString(); + _r1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonR.ToString(); + _zR1.Label = ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon.ButtonZr.ToString(); - _custThemePath.Buffer.Text = SwitchConfig.CustomThemePath; - _graphicsShadersDumpPath.Buffer.Text = SwitchConfig.GraphicsShadersDumpPath; - _fsLogSpinAdjustment.Value = SwitchConfig.FsGlobalAccessLogMode; + _custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath; + _graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath; + _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0); _gameDirsBoxStore = new ListStore(typeof(string)); _gameDirsBox.Model = _gameDirsBoxStore; - foreach (string gameDir in SwitchConfig.GameDirs) + foreach (string gameDir in ConfigurationState.Instance.Ui.GameDirs.Value) { _gameDirsBoxStore.AppendValues(gameDir); } @@ -208,7 +263,7 @@ namespace Ryujinx.Ui string key = keyPressed.Event.Key.ToString(); string capKey = key.First().ToString().ToUpper() + key.Substring(1); - if (Enum.IsDefined(typeof(OpenTK.Input.Key), capKey)) + if (Enum.IsDefined(typeof(Configuration.Hid.Key), capKey)) { button.Label = capKey; } @@ -325,65 +380,63 @@ namespace Ryujinx.Ui _gameDirsBoxStore.IterNext(ref treeIter); } - SwitchConfig.LoggingEnableError = _errorLogToggle.Active; - SwitchConfig.LoggingEnableWarn = _warningLogToggle.Active; - SwitchConfig.LoggingEnableInfo = _infoLogToggle.Active; - SwitchConfig.LoggingEnableStub = _stubLogToggle.Active; - SwitchConfig.LoggingEnableDebug = _debugLogToggle.Active; - SwitchConfig.LoggingEnableGuest = _guestLogToggle.Active; - SwitchConfig.LoggingEnableFsAccessLog = _fsAccessLogToggle.Active; - SwitchConfig.EnableFileLog = _fileLogToggle.Active; - SwitchConfig.DockedMode = _dockedModeToggle.Active; - SwitchConfig.EnableDiscordIntegration = _discordToggle.Active; - SwitchConfig.EnableVsync = _vSyncToggle.Active; - SwitchConfig.EnableMulticoreScheduling = _multiSchedToggle.Active; - SwitchConfig.EnableFsIntegrityChecks = _fsicToggle.Active; - SwitchConfig.IgnoreMissingServices = _ignoreToggle.Active; - SwitchConfig.EnableKeyboard = _directKeyboardAccess.Active; - SwitchConfig.EnableCustomTheme = _custThemeToggle.Active; + ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active; + ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active; + ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active; + ConfigurationState.Instance.Logger.EnableStub.Value = _stubLogToggle.Active; + ConfigurationState.Instance.Logger.EnableDebug.Value = _debugLogToggle.Active; + ConfigurationState.Instance.Logger.EnableGuest.Value = _guestLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Value = _fsAccessLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFileLog.Value = _fileLogToggle.Active; + ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active; + ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active; + ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; + ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active; + ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active; + ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active; + ConfigurationState.Instance.Hid.EnableKeyboard.Value = _directKeyboardAccess.Active; + ConfigurationState.Instance.Ui.EnableCustomTheme.Value = _custThemeToggle.Active; - SwitchConfig.KeyboardControls.LeftJoycon = new NpadKeyboardLeft() + ConfigurationState.Instance.Hid.KeyboardControls.Value.LeftJoycon = new NpadKeyboardLeft() { - StickUp = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickUp1.Label), - StickDown = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickDown1.Label), - StickLeft = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickLeft1.Label), - StickRight = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickRight1.Label), - StickButton = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _lStickButton1.Label), - DPadUp = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadUp1.Label), - DPadDown = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadDown1.Label), - DPadLeft = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadLeft1.Label), - DPadRight = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _dpadRight1.Label), - ButtonMinus = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _minus1.Label), - ButtonL = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _l1.Label), - ButtonZl = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _zL1.Label), + StickUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickUp1.Label), + StickDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickDown1.Label), + StickLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickLeft1.Label), + StickRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickRight1.Label), + StickButton = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _lStickButton1.Label), + DPadUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadUp1.Label), + DPadDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadDown1.Label), + DPadLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadLeft1.Label), + DPadRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _dpadRight1.Label), + ButtonMinus = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _minus1.Label), + ButtonL = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _l1.Label), + ButtonZl = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _zL1.Label), }; - SwitchConfig.KeyboardControls.RightJoycon = new NpadKeyboardRight() + ConfigurationState.Instance.Hid.KeyboardControls.Value.RightJoycon = new NpadKeyboardRight() { - StickUp = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickUp1.Label), - StickDown = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickDown1.Label), - StickLeft = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickLeft1.Label), - StickRight = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickRight1.Label), - StickButton = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _rStickButton1.Label), - ButtonA = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _a1.Label), - ButtonB = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _b1.Label), - ButtonX = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _x1.Label), - ButtonY = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _y1.Label), - ButtonPlus = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _plus1.Label), - ButtonR = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _r1.Label), - ButtonZr = (OpenTK.Input.Key)Enum.Parse(typeof(OpenTK.Input.Key), _zR1.Label), + StickUp = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickUp1.Label), + StickDown = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickDown1.Label), + StickLeft = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickLeft1.Label), + StickRight = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickRight1.Label), + StickButton = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _rStickButton1.Label), + ButtonA = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _a1.Label), + ButtonB = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _b1.Label), + ButtonX = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _x1.Label), + ButtonY = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _y1.Label), + ButtonPlus = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _plus1.Label), + ButtonR = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _r1.Label), + ButtonZr = (Configuration.Hid.Key)Enum.Parse(typeof(Configuration.Hid.Key), _zR1.Label), }; - SwitchConfig.SystemLanguage = (SystemLanguage)Enum.Parse(typeof(SystemLanguage), _systemLanguageSelect.ActiveId); - SwitchConfig.ControllerType = (ControllerStatus)Enum.Parse(typeof(ControllerStatus), _controller1Type.ActiveId); - SwitchConfig.CustomThemePath = _custThemePath.Buffer.Text; - SwitchConfig.GraphicsShadersDumpPath = _graphicsShadersDumpPath.Buffer.Text; - SwitchConfig.GameDirs = gameDirs; - SwitchConfig.FsGlobalAccessLogMode = (int)_fsLogSpinAdjustment.Value; - - Configuration.SaveConfig(SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json")); - Configuration.Configure(_device, SwitchConfig); + ConfigurationState.Instance.System.Language.Value = (Language)Enum.Parse(typeof(Language), _systemLanguageSelect.ActiveId); + ConfigurationState.Instance.Hid.ControllerType.Value = (ControllerType)Enum.Parse(typeof(ControllerType), _controller1Type.ActiveId); + ConfigurationState.Instance.Ui.CustomThemePath.Value = _custThemePath.Buffer.Text; + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text; + ConfigurationState.Instance.Ui.GameDirs.Value = gameDirs; + ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value; + MainWindow.SaveConfig(); MainWindow.ApplyTheme(); #pragma warning disable CS4014 MainWindow.UpdateGameTable();