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 ee80d892b0..24a122c33b 100644 --- a/ARMeilleure/CodeGen/X86/Assembler.cs +++ b/ARMeilleure/CodeGen/X86/Assembler.cs @@ -75,6 +75,10 @@ namespace ARMeilleure.CodeGen.X86 Add(X86Instruction.And, new InstructionInfo(0x00000021, 0x04000083, 0x04000081, BadOp, 0x00000023, InstructionFlags.None)); Add(X86Instruction.Andnpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f55, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Andnps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f55, InstructionFlags.Vex)); + Add(X86Instruction.Andpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f54, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Andps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f54, InstructionFlags.Vex)); + Add(X86Instruction.Blendvpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3815, InstructionFlags.Prefix66)); + Add(X86Instruction.Blendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3814, InstructionFlags.Prefix66)); Add(X86Instruction.Bsr, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbd, InstructionFlags.None)); Add(X86Instruction.Bswap, new InstructionInfo(0x00000fc8, BadOp, BadOp, BadOp, BadOp, InstructionFlags.RegOnly)); Add(X86Instruction.Call, new InstructionInfo(0x020000ff, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); @@ -245,6 +249,8 @@ namespace ARMeilleure.CodeGen.X86 Add(X86Instruction.Unpckhps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f15, InstructionFlags.Vex)); Add(X86Instruction.Unpcklpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f14, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Unpcklps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f14, InstructionFlags.Vex)); + Add(X86Instruction.Vblendvpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vblendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4a, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66)); Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None)); Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66)); diff --git a/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/ARMeilleure/CodeGen/X86/CodeGenerator.cs index 33fc2aee3e..d1224363c0 100644 --- a/ARMeilleure/CodeGen/X86/CodeGenerator.cs +++ b/ARMeilleure/CodeGen/X86/CodeGenerator.cs @@ -336,7 +336,15 @@ namespace ARMeilleure.CodeGen.X86 Debug.Assert(!dest.Type.IsInteger()); - if (info.Inst == X86Instruction.Pblendvb && HardwareCapabilities.SupportsVexEncoding) + if (info.Inst == X86Instruction.Blendvpd && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vblendvpd, dest, src1, src2, src3); + } + else if (info.Inst == X86Instruction.Blendvps && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vblendvps, dest, src1, src2, src3); + } + else if (info.Inst == X86Instruction.Pblendvb && HardwareCapabilities.SupportsVexEncoding) { context.Assembler.WriteInstruction(X86Instruction.Vpblendvb, dest, src1, src2, src3); } @@ -1646,7 +1654,7 @@ namespace ARMeilleure.CodeGen.X86 for (int offset = PageSize; offset < size; offset += PageSize) { - Operand memOp = new MemoryOperand(OperandType.I32, rsp, null, Multiplier.x1, -offset);; + Operand memOp = new MemoryOperand(OperandType.I32, rsp, null, Multiplier.x1, -offset); context.Assembler.Mov(temp, memOp, OperandType.I32); } diff --git a/ARMeilleure/CodeGen/X86/IntrinsicTable.cs b/ARMeilleure/CodeGen/X86/IntrinsicTable.cs index 73fb5fd1b6..e87de03509 100644 --- a/ARMeilleure/CodeGen/X86/IntrinsicTable.cs +++ b/ARMeilleure/CodeGen/X86/IntrinsicTable.cs @@ -19,6 +19,10 @@ namespace ARMeilleure.CodeGen.X86 Add(Intrinsic.X86Addss, new IntrinsicInfo(X86Instruction.Addss, IntrinsicType.Binary)); Add(Intrinsic.X86Andnpd, new IntrinsicInfo(X86Instruction.Andnpd, IntrinsicType.Binary)); Add(Intrinsic.X86Andnps, new IntrinsicInfo(X86Instruction.Andnps, IntrinsicType.Binary)); + Add(Intrinsic.X86Andpd, new IntrinsicInfo(X86Instruction.Andpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Andps, new IntrinsicInfo(X86Instruction.Andps, IntrinsicType.Binary)); + Add(Intrinsic.X86Blendvpd, new IntrinsicInfo(X86Instruction.Blendvpd, IntrinsicType.Ternary)); + Add(Intrinsic.X86Blendvps, new IntrinsicInfo(X86Instruction.Blendvps, IntrinsicType.Ternary)); Add(Intrinsic.X86Cmppd, new IntrinsicInfo(X86Instruction.Cmppd, IntrinsicType.TernaryImm)); Add(Intrinsic.X86Cmpps, new IntrinsicInfo(X86Instruction.Cmpps, IntrinsicType.TernaryImm)); Add(Intrinsic.X86Cmpsd, new IntrinsicInfo(X86Instruction.Cmpsd, IntrinsicType.TernaryImm)); diff --git a/ARMeilleure/CodeGen/X86/PreAllocator.cs b/ARMeilleure/CodeGen/X86/PreAllocator.cs index a149013115..034a87ac25 100644 --- a/ARMeilleure/CodeGen/X86/PreAllocator.cs +++ b/ARMeilleure/CodeGen/X86/PreAllocator.cs @@ -298,8 +298,11 @@ namespace ARMeilleure.CodeGen.X86 { IntrinsicOperation intrinOp = (IntrinsicOperation)operation; - // PBLENDVB last operand is always implied to be XMM0 when VEX is not supported. - if (intrinOp.Intrinsic == Intrinsic.X86Pblendvb && !HardwareCapabilities.SupportsVexEncoding) + // BLENDVPD, BLENDVPS, PBLENDVB last operand is always implied to be XMM0 when VEX is not supported. + if ((intrinOp.Intrinsic == Intrinsic.X86Blendvpd || + intrinOp.Intrinsic == Intrinsic.X86Blendvps || + intrinOp.Intrinsic == Intrinsic.X86Pblendvb) && + !HardwareCapabilities.SupportsVexEncoding) { Operand xmm0 = Xmm(X86Register.Xmm0, OperandType.V128); diff --git a/ARMeilleure/CodeGen/X86/X86Instruction.cs b/ARMeilleure/CodeGen/X86/X86Instruction.cs index 10ba891aa5..a29e68fb6f 100644 --- a/ARMeilleure/CodeGen/X86/X86Instruction.cs +++ b/ARMeilleure/CodeGen/X86/X86Instruction.cs @@ -10,6 +10,10 @@ namespace ARMeilleure.CodeGen.X86 And, Andnpd, Andnps, + Andpd, + Andps, + Blendvpd, + Blendvps, Bsr, Bswap, Call, @@ -180,6 +184,8 @@ namespace ARMeilleure.CodeGen.X86 Unpckhps, Unpcklpd, Unpcklps, + Vblendvpd, + Vblendvps, Vpblendvb, Xor, Xorpd, diff --git a/ARMeilleure/Common/BitUtils.cs b/ARMeilleure/Common/BitUtils.cs index 55344608cc..7a29dcff7f 100644 --- a/ARMeilleure/Common/BitUtils.cs +++ b/ARMeilleure/Common/BitUtils.cs @@ -1,12 +1,12 @@ -using System.Runtime.CompilerServices; - namespace ARMeilleure.Common { static class BitUtils { private const int DeBrujinSequence = 0x77cb531; - private static int[] DeBrujinLbsLut; + private static readonly int[] DeBrujinLbsLut; + + private static readonly sbyte[] HbsNibbleLut; static BitUtils() { @@ -18,19 +18,27 @@ namespace ARMeilleure.Common DeBrujinLbsLut[lutIndex] = index; } + + HbsNibbleLut = new sbyte[] { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 }; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LowestBitSet(int value) + public static int CountBits(int value) { - if (value == 0) + int count = 0; + + while (value != 0) { - return -1; + value &= ~(value & -value); + + count++; } - int lsb = value & -value; + return count; + } - return DeBrujinLbsLut[(uint)(DeBrujinSequence * lsb) >> 27]; + public static long FillWithOnes(int bits) + { + return bits == 64 ? -1L : (1L << bits) - 1; } public static int HighestBitSet(int value) @@ -51,9 +59,22 @@ namespace ARMeilleure.Common return -1; } - private static readonly sbyte[] HbsNibbleLut = { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 }; + public static int HighestBitSetNibble(int value) + { + return HbsNibbleLut[value]; + } - public static int HighestBitSetNibble(int value) => HbsNibbleLut[value & 0b1111]; + public static int LowestBitSet(int value) + { + if (value == 0) + { + return -1; + } + + int lsb = value & -value; + + return DeBrujinLbsLut[(uint)(DeBrujinSequence * lsb) >> 27]; + } public static long Replicate(long bits, int size) { @@ -67,25 +88,6 @@ namespace ARMeilleure.Common return output; } - public static int CountBits(int value) - { - int count = 0; - - while (value != 0) - { - value &= ~(value & -value); - - count++; - } - - return count; - } - - public static long FillWithOnes(int bits) - { - return bits == 64 ? -1L : (1L << bits) - 1; - } - public static int RotateRight(int bits, int shift, int size) { return (int)RotateRight((uint)bits, shift, size); 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/Decoders/DecoderHelper.cs b/ARMeilleure/Decoders/DecoderHelper.cs index 3cbd49123d..bc41c61c6a 100644 --- a/ARMeilleure/Decoders/DecoderHelper.cs +++ b/ARMeilleure/Decoders/DecoderHelper.cs @@ -1,10 +1,77 @@ using ARMeilleure.Common; -using System; namespace ARMeilleure.Decoders { static class DecoderHelper { + static DecoderHelper() + { + Imm8ToFP32Table = BuildImm8ToFP32Table(); + Imm8ToFP64Table = BuildImm8ToFP64Table(); + } + + public static readonly uint[] Imm8ToFP32Table; + public static readonly ulong[] Imm8ToFP64Table; + + private static uint[] BuildImm8ToFP32Table() + { + uint[] tbl = new uint[256]; + + for (int idx = 0; idx < 256; idx++) + { + tbl[idx] = ExpandImm8ToFP32((uint)idx); + } + + return tbl; + } + + private static ulong[] BuildImm8ToFP64Table() + { + ulong[] tbl = new ulong[256]; + + for (int idx = 0; idx < 256; idx++) + { + tbl[idx] = ExpandImm8ToFP64((ulong)idx); + } + + return tbl; + } + + // abcdefgh -> aBbbbbbc defgh000 00000000 00000000 (B = ~b) + private static uint ExpandImm8ToFP32(uint imm) + { + uint MoveBit(uint bits, int from, int to) + { + return ((bits >> from) & 1U) << to; + } + + return MoveBit(imm, 7, 31) | MoveBit(~imm, 6, 30) | + MoveBit(imm, 6, 29) | MoveBit( imm, 6, 28) | + MoveBit(imm, 6, 27) | MoveBit( imm, 6, 26) | + MoveBit(imm, 6, 25) | MoveBit( imm, 5, 24) | + MoveBit(imm, 4, 23) | MoveBit( imm, 3, 22) | + MoveBit(imm, 2, 21) | MoveBit( imm, 1, 20) | + MoveBit(imm, 0, 19); + } + + // abcdefgh -> aBbbbbbb bbcdefgh 00000000 00000000 00000000 00000000 00000000 00000000 (B = ~b) + private static ulong ExpandImm8ToFP64(ulong imm) + { + ulong MoveBit(ulong bits, int from, int to) + { + return ((bits >> from) & 1UL) << to; + } + + return MoveBit(imm, 7, 63) | MoveBit(~imm, 6, 62) | + MoveBit(imm, 6, 61) | MoveBit( imm, 6, 60) | + MoveBit(imm, 6, 59) | MoveBit( imm, 6, 58) | + MoveBit(imm, 6, 57) | MoveBit( imm, 6, 56) | + MoveBit(imm, 6, 55) | MoveBit( imm, 6, 54) | + MoveBit(imm, 5, 53) | MoveBit( imm, 4, 52) | + MoveBit(imm, 3, 51) | MoveBit( imm, 2, 50) | + MoveBit(imm, 1, 49) | MoveBit( imm, 0, 48); + } + public struct BitMask { public long WMask; @@ -62,34 +129,6 @@ namespace ARMeilleure.Decoders }; } - public static long DecodeImm8Float(long imm, int size) - { - int e = 0, f = 0; - - switch (size) - { - case 0: e = 8; f = 23; break; - case 1: e = 11; f = 52; break; - - default: throw new ArgumentOutOfRangeException(nameof(size)); - } - - long value = (imm & 0x3f) << f - 4; - - long eBit = (imm >> 6) & 1; - long sBit = (imm >> 7) & 1; - - if (eBit != 0) - { - value |= (1L << e - 3) - 1 << f + 2; - } - - value |= (eBit ^ 1) << f + e - 1; - value |= sBit << f + e; - - return value; - } - public static long DecodeImm24_2(int opCode) { return ((long)opCode << 40) >> 38; diff --git a/ARMeilleure/Decoders/OpCodeSimdFmov.cs b/ARMeilleure/Decoders/OpCodeSimdFmov.cs index 61a3f077d9..f0da03968d 100644 --- a/ARMeilleure/Decoders/OpCodeSimdFmov.cs +++ b/ARMeilleure/Decoders/OpCodeSimdFmov.cs @@ -8,16 +8,8 @@ namespace ARMeilleure.Decoders public OpCodeSimdFmov(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { - int imm5 = (opCode >> 5) & 0x1f; int type = (opCode >> 22) & 0x3; - if (imm5 != 0b00000 || type > 1) - { - Instruction = InstDescriptor.Undefined; - - return; - } - Size = type; long imm; @@ -25,7 +17,14 @@ namespace ARMeilleure.Decoders Rd = (opCode >> 0) & 0x1f; imm = (opCode >> 13) & 0xff; - Immediate = DecoderHelper.DecodeImm8Float(imm, type); + if (type == 0) + { + Immediate = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + } + else /* if (type == 1) */ + { + Immediate = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + } } } } \ No newline at end of file diff --git a/ARMeilleure/Decoders/OpCodeSimdImm.cs b/ARMeilleure/Decoders/OpCodeSimdImm.cs index ecad906d91..a88e360ee4 100644 --- a/ARMeilleure/Decoders/OpCodeSimdImm.cs +++ b/ARMeilleure/Decoders/OpCodeSimdImm.cs @@ -23,19 +23,19 @@ namespace ARMeilleure.Decoders if (modeHigh == 0b111) { - Size = modeLow != 0 ? op : 3; - switch (op | (modeLow << 1)) { case 0: // 64-bits Immediate. // Transform abcd efgh into abcd efgh abcd efgh ... + Size = 3; imm = (long)((ulong)imm * 0x0101010101010101); break; case 1: // 64-bits Immediate. // Transform abcd efgh into aaaa aaaa bbbb bbbb ... + Size = 3; imm = (imm & 0xf0) >> 4 | (imm & 0x0f) << 4; imm = (imm & 0xcc) >> 2 | (imm & 0x33) << 2; imm = (imm & 0xaa) >> 1 | (imm & 0x55) << 1; @@ -49,9 +49,16 @@ namespace ARMeilleure.Decoders break; case 2: + // 2 x 32-bits floating point Immediate. + Size = 0; + imm = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + imm |= imm << 32; + break; + case 3: - // Floating point Immediate. - imm = DecoderHelper.DecodeImm8Float(imm, Size); + // 64-bits floating point Immediate. + Size = 1; + imm = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; break; } } @@ -72,7 +79,7 @@ namespace ARMeilleure.Decoders } else { - // 8 bits without shift. + // 8-bits without shift. Size = 0; } diff --git a/ARMeilleure/Instructions/InstEmitAluHelper.cs b/ARMeilleure/Instructions/InstEmitAluHelper.cs index 81d5c9eb34..d032b32e87 100644 --- a/ARMeilleure/Instructions/InstEmitAluHelper.cs +++ b/ARMeilleure/Instructions/InstEmitAluHelper.cs @@ -268,7 +268,7 @@ namespace ARMeilleure.Instructions { if (setCarry) { - SetFlag(context, PState.CFlag, Const(0));; + SetFlag(context, PState.CFlag, Const(0)); } return Const(0); diff --git a/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs index c411a6d3fc..1a9e01c8da 100644 --- a/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs +++ b/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -384,8 +384,7 @@ namespace ARMeilleure.Instructions } else { - OperandType type = sizeF != 0 ? OperandType.FP64 - : OperandType.FP32; + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; Operand ne0 = context.VectorExtract(type, GetVec(op.Rn), 0); Operand ne1 = context.VectorExtract(type, GetVec(op.Rn), 1); @@ -455,6 +454,7 @@ namespace ARMeilleure.Instructions { OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + Operand d = GetVec(op.Rd); Operand a = GetVec(op.Ra); Operand n = GetVec(op.Rn); Operand m = GetVec(op.Rm); @@ -462,18 +462,16 @@ namespace ARMeilleure.Instructions if (op.Size == 0) { Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addss, a, res); - res = context.AddIntrinsic(Intrinsic.X86Addss, a, res); - - context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + context.Copy(d, context.VectorZeroUpper96(res)); } else /* if (op.Size == 1) */ { Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res); - res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res); - - context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + context.Copy(d, context.VectorZeroUpper64(res)); } } else @@ -517,18 +515,32 @@ namespace ARMeilleure.Instructions public static void Fmaxnm_S(ArmEmitterContext context) { - EmitScalarBinaryOpF(context, (op1, op2) => + if (Optimizations.FastFP && Optimizations.UseSse41) { - return EmitSoftFloatCall(context, SoftFloat32.FPMaxNum, SoftFloat64.FPMaxNum, op1, op2); - }); + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMaxNum, SoftFloat64.FPMaxNum, op1, op2); + }); + } } public static void Fmaxnm_V(ArmEmitterContext context) { - EmitVectorBinaryOpF(context, (op1, op2) => + if (Optimizations.FastFP && Optimizations.UseSse41) { - return EmitSoftFloatCall(context, SoftFloat32.FPMaxNum, SoftFloat64.FPMaxNum, op1, op2); - }); + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMaxNum, SoftFloat64.FPMaxNum, op1, op2); + }); + } } public static void Fmaxp_V(ArmEmitterContext context) @@ -578,18 +590,32 @@ namespace ARMeilleure.Instructions public static void Fminnm_S(ArmEmitterContext context) { - EmitScalarBinaryOpF(context, (op1, op2) => + if (Optimizations.FastFP && Optimizations.UseSse41) { - return EmitSoftFloatCall(context, SoftFloat32.FPMinNum, SoftFloat64.FPMinNum, op1, op2); - }); + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMinNum, SoftFloat64.FPMinNum, op1, op2); + }); + } } public static void Fminnm_V(ArmEmitterContext context) { - EmitVectorBinaryOpF(context, (op1, op2) => + if (Optimizations.FastFP && Optimizations.UseSse41) { - return EmitSoftFloatCall(context, SoftFloat32.FPMinNum, SoftFloat64.FPMinNum, op1, op2); - }); + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPMinNum, SoftFloat64.FPMinNum, op1, op2); + }); + } } public static void Fminp_V(ArmEmitterContext context) @@ -813,6 +839,7 @@ namespace ARMeilleure.Instructions { OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + Operand d = GetVec(op.Rd); Operand a = GetVec(op.Ra); Operand n = GetVec(op.Rn); Operand m = GetVec(op.Rm); @@ -820,18 +847,16 @@ namespace ARMeilleure.Instructions if (op.Size == 0) { Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, a, res); - res = context.AddIntrinsic(Intrinsic.X86Subss, a, res); - - context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + context.Copy(d, context.VectorZeroUpper96(res)); } else /* if (op.Size == 1) */ { Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res); - res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res); - - context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + context.Copy(d, context.VectorZeroUpper64(res)); } } else @@ -1035,36 +1060,88 @@ namespace ARMeilleure.Instructions public static void Fnmadd_S(ArmEmitterContext context) // Fused. { - OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; - int sizeF = op.Size & 1; + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); - OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + if (op.Size == 0) + { + Operand mask = X86GetScalar(context, -0f); - Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); - Operand me = context.VectorExtract(type, GetVec(op.Rm), 0); - Operand ae = context.VectorExtract(type, GetVec(op.Ra), 0); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); - Operand res = context.Subtract(context.Multiply(context.Negate(ne), me), ae); + Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res); - context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + Operand mask = X86GetScalar(context, -0d); + + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res); + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPNegMulAdd, SoftFloat64.FPNegMulAdd, op1, op2, op3); + }); + } } public static void Fnmsub_S(ArmEmitterContext context) // Fused. { - OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; - int sizeF = op.Size & 1; + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); - OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + if (op.Size == 0) + { + Operand mask = X86GetScalar(context, -0f); - Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); - Operand me = context.VectorExtract(type, GetVec(op.Rm), 0); - Operand ae = context.VectorExtract(type, GetVec(op.Ra), 0); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); - Operand res = context.Subtract(context.Multiply(ne, me), ae); + Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res); - context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + Operand mask = X86GetScalar(context, -0d); + + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res); + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, SoftFloat32.FPNegMulSub, SoftFloat64.FPNegMulSub, op1, op2, op3); + }); + } } public static void Fnmul_S(ArmEmitterContext context) @@ -2067,9 +2144,7 @@ namespace ARMeilleure.Instructions m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); } - Intrinsic movInst = op.Size == 0 - ? Intrinsic.X86Pmovsxbw - : Intrinsic.X86Pmovsxwd; + Intrinsic movInst = op.Size == 0 ? Intrinsic.X86Pmovsxbw : Intrinsic.X86Pmovsxwd; n = context.AddIntrinsic(movInst, n); m = context.AddIntrinsic(movInst, m); @@ -2694,9 +2769,7 @@ namespace ARMeilleure.Instructions m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); } - Intrinsic movInst = op.Size == 0 - ? Intrinsic.X86Pmovzxbw - : Intrinsic.X86Pmovzxwd; + Intrinsic movInst = op.Size == 0 ? Intrinsic.X86Pmovzxbw : Intrinsic.X86Pmovzxwd; n = context.AddIntrinsic(movInst, n); m = context.AddIntrinsic(movInst, m); @@ -3011,6 +3084,98 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } + private static Operand EmitSse2VectorIsQNaNOpF(ArmEmitterContext context, Operand opF) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + const int QBit = 22; + + Operand qMask = X86GetAllElements(context, 1 << QBit); + + Operand mask1 = context.AddIntrinsic(Intrinsic.X86Cmpps, opF, opF, Const((int)CmpCondition.UnorderedQ)); + + Operand mask2 = context.AddIntrinsic(Intrinsic.X86Pand, opF, qMask); + mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, mask2, qMask, Const((int)CmpCondition.Equal)); + + return context.AddIntrinsic(Intrinsic.X86Andps, mask1, mask2); + } + else /* if ((op.Size & 1) == 1) */ + { + const int QBit = 51; + + Operand qMask = X86GetAllElements(context, 1L << QBit); + + Operand mask1 = context.AddIntrinsic(Intrinsic.X86Cmppd, opF, opF, Const((int)CmpCondition.UnorderedQ)); + + Operand mask2 = context.AddIntrinsic(Intrinsic.X86Pand, opF, qMask); + mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, mask2, qMask, Const((int)CmpCondition.Equal)); + + return context.AddIntrinsic(Intrinsic.X86Andpd, mask1, mask2); + } + } + + private static void EmitSse41MaxMinNumOpF(ArmEmitterContext context, bool isMaxNum, bool scalar) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + 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); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand negInfMask = X86GetAllElements(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnps, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnps, nQNaNMask, mQNaNMask); + + nNum = context.AddIntrinsic(Intrinsic.X86Blendvps, nNum, negInfMask, nMask); + mNum = context.AddIntrinsic(Intrinsic.X86Blendvps, mNum, negInfMask, mMask); + + Operand res = context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxps : Intrinsic.X86Minps, nNum, mNum); + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + Operand negInfMask = X86GetAllElements(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnpd, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnpd, nQNaNMask, mQNaNMask); + + nNum = context.AddIntrinsic(Intrinsic.X86Blendvpd, nNum, negInfMask, nMask); + mNum = context.AddIntrinsic(Intrinsic.X86Blendvpd, mNum, negInfMask, mMask); + + Operand res = context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxpd : Intrinsic.X86Minpd, nNum, mNum); + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + } + private enum AddSub { None, diff --git a/ARMeilleure/Instructions/InstEmitSimdCmp.cs b/ARMeilleure/Instructions/InstEmitSimdCmp.cs index ac1bffcb2e..e70f56a0a3 100644 --- a/ARMeilleure/Instructions/InstEmitSimdCmp.cs +++ b/ARMeilleure/Instructions/InstEmitSimdCmp.cs @@ -300,7 +300,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitCmpSseOrSse2OpF(context, CmpCondition.Equal, scalar: true); + EmitSse2CmpOpF(context, CmpCondition.Equal, scalar: true); } else { @@ -312,7 +312,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitCmpSseOrSse2OpF(context, CmpCondition.Equal, scalar: false); + EmitSse2CmpOpF(context, CmpCondition.Equal, scalar: false); } else { @@ -324,7 +324,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseAvx) { - EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThanOrEqual, scalar: true); + EmitSse2CmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: true); } else { @@ -336,7 +336,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseAvx) { - EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThanOrEqual, scalar: false); + EmitSse2CmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: false); } else { @@ -348,7 +348,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseAvx) { - EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThan, scalar: true); + EmitSse2CmpOpF(context, CmpCondition.GreaterThan, scalar: true); } else { @@ -360,7 +360,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseAvx) { - EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThan, scalar: false); + EmitSse2CmpOpF(context, CmpCondition.GreaterThan, scalar: false); } else { @@ -372,7 +372,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitCmpSseOrSse2OpF(context, CmpCondition.LessThanOrEqual, scalar: true); + EmitSse2CmpOpF(context, CmpCondition.LessThanOrEqual, scalar: true); } else { @@ -384,7 +384,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitCmpSseOrSse2OpF(context, CmpCondition.LessThanOrEqual, scalar: false); + EmitSse2CmpOpF(context, CmpCondition.LessThanOrEqual, scalar: false); } else { @@ -396,7 +396,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitCmpSseOrSse2OpF(context, CmpCondition.LessThan, scalar: true); + EmitSse2CmpOpF(context, CmpCondition.LessThan, scalar: true); } else { @@ -408,7 +408,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.FastFP && Optimizations.UseSse2) { - EmitCmpSseOrSse2OpF(context, CmpCondition.LessThan, scalar: false); + EmitSse2CmpOpF(context, CmpCondition.LessThan, scalar: false); } else { @@ -673,7 +673,7 @@ namespace ARMeilleure.Instructions context.Copy(GetVec(op.Rd), res); } - private static void EmitCmpSseOrSse2OpF(ArmEmitterContext context, CmpCondition cond, bool scalar) + private static void EmitSse2CmpOpF(ArmEmitterContext context, CmpCondition cond, bool scalar) { OpCodeSimd op = (OpCodeSimd)context.CurrOp; diff --git a/ARMeilleure/Instructions/InstEmitSimdHelper.cs b/ARMeilleure/Instructions/InstEmitSimdHelper.cs index f0880079e4..28d075dd3b 100644 --- a/ARMeilleure/Instructions/InstEmitSimdHelper.cs +++ b/ARMeilleure/Instructions/InstEmitSimdHelper.cs @@ -907,7 +907,7 @@ namespace ARMeilleure.Instructions Operand res = context.VectorZero(); - Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed);; + Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed); int elems = 8 >> op.Size; @@ -939,7 +939,7 @@ namespace ARMeilleure.Instructions Operand res = context.VectorZero(); - Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed);; + Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed); int elems = 8 >> op.Size; @@ -1114,6 +1114,7 @@ namespace ARMeilleure.Instructions Equal = 0, // Ordered, non-signaling. LessThan = 1, // Ordered, signaling. LessThanOrEqual = 2, // Ordered, signaling. + UnorderedQ = 3, // Non-signaling. NotLessThan = 5, // Unordered, signaling. NotLessThanOrEqual = 6, // Unordered, signaling. OrderedQ = 7, // Non-signaling. diff --git a/ARMeilleure/Instructions/InstEmitSimdMove.cs b/ARMeilleure/Instructions/InstEmitSimdMove.cs index 9d2aeb3bde..789c8c8712 100644 --- a/ARMeilleure/Instructions/InstEmitSimdMove.cs +++ b/ARMeilleure/Instructions/InstEmitSimdMove.cs @@ -177,7 +177,7 @@ namespace ARMeilleure.Instructions if (op.RegisterSize == RegisterSize.Simd64) { - nShifted = context.AddIntrinsic(Intrinsic.X86Movlhps, nShifted, context.VectorZero()); + nShifted = context.VectorZeroUpper64(nShifted); } nShifted = context.AddIntrinsic(Intrinsic.X86Psrldq, nShifted, Const(op.Imm4)); @@ -188,7 +188,7 @@ namespace ARMeilleure.Instructions if (op.RegisterSize == RegisterSize.Simd64) { - mShifted = context.AddIntrinsic(Intrinsic.X86Movlhps, mShifted, context.VectorZero()); + mShifted = context.VectorZeroUpper64(mShifted); } Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, mShifted); @@ -277,9 +277,10 @@ namespace ARMeilleure.Instructions { OpCodeSimd op = (OpCodeSimd)context.CurrOp; + Operand d = GetVec(op.Rd); Operand n = GetIntOrZR(context, op.Rn); - context.Copy(GetVec(op.Rd), EmitVectorInsert(context, GetVec(op.Rd), n, 1, 3)); + context.Copy(d, EmitVectorInsert(context, d, n, 1, 3)); } public static void Fmov_S(ArmEmitterContext context) @@ -311,18 +312,32 @@ namespace ARMeilleure.Instructions { OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; - Operand e = Const(op.Immediate); - - Operand res = context.VectorZero(); - - int elems = op.RegisterSize == RegisterSize.Simd128 ? 4 : 2; - - for (int index = 0; index < (elems >> op.Size); index++) + if (Optimizations.UseSse2) { - res = EmitVectorInsert(context, res, e, index, op.Size + 2); + if (op.RegisterSize == RegisterSize.Simd128) + { + context.Copy(GetVec(op.Rd), X86GetAllElements(context, op.Immediate)); + } + else + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, op.Immediate)); + } } + else + { + Operand e = Const(op.Immediate); - context.Copy(GetVec(op.Rd), res); + Operand res = context.VectorZero(); + + int elems = op.RegisterSize == RegisterSize.Simd128 ? 2 : 1; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, e, index, 3); + } + + context.Copy(GetVec(op.Rd), res); + } } public static void Ins_Gp(ArmEmitterContext context) @@ -349,7 +364,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.UseSse2) { - EmitMoviMvni(context, not: false); + EmitSse2MoviMvni(context, not: false); } else { @@ -361,7 +376,7 @@ namespace ARMeilleure.Instructions { if (Optimizations.UseSse2) { - EmitMoviMvni(context, not: true); + EmitSse2MoviMvni(context, not: true); } else { @@ -430,13 +445,11 @@ namespace ARMeilleure.Instructions { Operand d = GetVec(op.Rd); - Operand res = context.AddIntrinsic(Intrinsic.X86Movlhps, d, context.VectorZero()); - - Operand n = GetVec(op.Rn); + Operand res = context.VectorZeroUpper64(d); Operand mask = X86GetAllElements(context, _masksE0_TrnUzpXtn[op.Size]); - Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, GetVec(op.Rn), mask); Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 ? Intrinsic.X86Movlhps @@ -444,7 +457,7 @@ namespace ARMeilleure.Instructions res = context.AddIntrinsic(movInst, res, res2); - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } else { @@ -452,7 +465,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++) { @@ -461,7 +476,7 @@ namespace ARMeilleure.Instructions res = EmitVectorInsert(context, res, ne, part + index, op.Size); } - context.Copy(GetVec(op.Rd), res); + context.Copy(d, res); } } @@ -475,7 +490,7 @@ namespace ARMeilleure.Instructions EmitVectorZip(context, part: 1); } - private static void EmitMoviMvni(ArmEmitterContext context, bool not) + private static void EmitSse2MoviMvni(ArmEmitterContext context, bool not) { OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; diff --git a/ARMeilleure/Instructions/SoftFloat.cs b/ARMeilleure/Instructions/SoftFloat.cs index af22c85d21..256bc5b975 100644 --- a/ARMeilleure/Instructions/SoftFloat.cs +++ b/ARMeilleure/Instructions/SoftFloat.cs @@ -1089,8 +1089,6 @@ namespace ARMeilleure.Instructions public static float FPMulSub(float valueA, float value1, float value2) { - ExecutionContext context = NativeInterface.GetContext(); - value1 = value1.FPNeg(); return FPMulAdd(valueA, value1, value2); @@ -1138,6 +1136,21 @@ namespace ARMeilleure.Instructions return result; } + public static float FPNegMulAdd(float valueA, float value1, float value2) + { + valueA = valueA.FPNeg(); + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPNegMulSub(float valueA, float value1, float value2) + { + valueA = valueA.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + public static float FPRecipEstimate(float value) { ExecutionContext context = NativeInterface.GetContext(); @@ -2196,6 +2209,21 @@ namespace ARMeilleure.Instructions return result; } + public static double FPNegMulAdd(double valueA, double value1, double value2) + { + valueA = valueA.FPNeg(); + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPNegMulSub(double valueA, double value1, double value2) + { + valueA = valueA.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + public static double FPRecipEstimate(double value) { ExecutionContext context = NativeInterface.GetContext(); diff --git a/ARMeilleure/IntermediateRepresentation/Intrinsic.cs b/ARMeilleure/IntermediateRepresentation/Intrinsic.cs index e2d3c6dbaf..57c8914d9c 100644 --- a/ARMeilleure/IntermediateRepresentation/Intrinsic.cs +++ b/ARMeilleure/IntermediateRepresentation/Intrinsic.cs @@ -8,6 +8,10 @@ namespace ARMeilleure.IntermediateRepresentation X86Addss, X86Andnpd, X86Andnps, + X86Andpd, + X86Andps, + X86Blendvpd, + X86Blendvps, X86Cmppd, X86Cmpps, X86Cmpsd, 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/Applets/SoftwareKeyboard/InitialCursorPosition.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs new file mode 100644 index 0000000000..727b6d27b1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the initial position of the cursor displayed in the area. + /// + enum InitialCursorPosition : uint + { + /// + /// Position the cursor at the beginning of the text + /// + Start, + + /// + /// Position the cursor at the end of the text + /// + End + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs new file mode 100644 index 0000000000..c3ce2c1251 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the text entry mode. + /// + enum InputFormMode : uint + { + /// + /// Displays the text entry area as a single-line field. + /// + SingleLine, + + /// + /// Displays the text entry area as a multi-line field. + /// + MultiLine + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs new file mode 100644 index 0000000000..f3fd8ac85d --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies prohibited character sets. + /// + [Flags] + enum InvalidCharFlags : uint + { + /// + /// No characters are prohibited. + /// + None = 0 << 1, + + /// + /// Prohibits spaces. + /// + Space = 1 << 1, + + /// + /// Prohibits the at (@) symbol. + /// + AtSymbol = 1 << 2, + + /// + /// Prohibits the percent (%) symbol. + /// + Percent = 1 << 3, + + /// + /// Prohibits the forward slash (/) symbol. + /// + ForwardSlash = 1 << 4, + + /// + /// Prohibits the backward slash (\) symbol. + /// + BackSlash = 1 << 5, + + /// + /// Prohibits numbers. + /// + Numbers = 1 << 6, + + /// + /// Prohibits characters outside of those allowed in download codes. + /// + DownloadCode = 1 << 7, + + /// + /// Prohibits characters outside of those allowed in Mii Nicknames. + /// + Username = 1 << 8 + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs new file mode 100644 index 0000000000..e5418a6f34 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the variant of keyboard displayed on screen. + /// + enum KeyboardMode : uint + { + /// + /// A full alpha-numeric keyboard. + /// + Default, + + /// + /// Number pad. + /// + NumbersOnly, + + /// + /// QWERTY (and variants) keyboard only. + /// + LettersOnly, + + /// + /// Unknown keyboard variant. + /// + Unknown + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs new file mode 100644 index 0000000000..fc9e1ff8e2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the display mode of text in a password field. + /// + enum PasswordMode : uint + { + /// + /// Display input characters. + /// + Disabled, + + /// + /// Hide input characters. + /// + Enabled + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs index 22fbe8d0a8..2780446a8b 100644 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs @@ -9,11 +9,11 @@ namespace Ryujinx.HLE.HOS.Applets { internal class SoftwareKeyboardApplet : IApplet { - private const string DEFAULT_NUMB = "1"; - private const string DEFAULT_TEXT = "Ryujinx"; + private const string DefaultNumb = "1"; + private const string DefaultText = "Ryujinx"; - private const int STANDARD_BUFFER_SIZE = 0x7D8; - private const int INTERACTIVE_BUFFER_SIZE = 0x7D4; + private const int StandardBufferSize = 0x7D8; + private const int InteractiveBufferSize = 0x7D4; private SoftwareKeyboardState _state = SoftwareKeyboardState.Uninitialized; @@ -22,7 +22,8 @@ namespace Ryujinx.HLE.HOS.Applets private SoftwareKeyboardConfig _keyboardConfig; - private string _textValue = DEFAULT_TEXT; + private string _textValue = DefaultText; + private Encoding _encoding = Encoding.Unicode; public event EventHandler AppletStateChanged; @@ -42,6 +43,11 @@ namespace Ryujinx.HLE.HOS.Applets _keyboardConfig = ReadStruct(keyboardConfig); + if (_keyboardConfig.UseUtf8) + { + _encoding = Encoding.UTF8; + } + _state = SoftwareKeyboardState.Ready; Execute(); @@ -58,9 +64,9 @@ namespace Ryujinx.HLE.HOS.Applets { // If the keyboard type is numbers only, we swap to a default // text that only contains numbers. - if (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly) + if (_keyboardConfig.Mode == KeyboardMode.NumbersOnly) { - _textValue = DEFAULT_NUMB; + _textValue = DefaultNumb; } // If the max string length is 0, we set it to a large default @@ -70,6 +76,15 @@ namespace Ryujinx.HLE.HOS.Applets _keyboardConfig.StringLengthMax = 100; } + // If the game requests a string with a minimum length less + // than our default text, repeat our default text until we meet + // the minimum length requirement. + // This should always be done before the text truncation step. + while (_textValue.Length < _keyboardConfig.StringLengthMin) + { + _textValue = String.Join(" ", _textValue, _textValue); + } + // If our default text is longer than the allowed length, // we truncate it. if (_textValue.Length > _keyboardConfig.StringLengthMax) @@ -77,7 +92,18 @@ namespace Ryujinx.HLE.HOS.Applets _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax); } - if (!_keyboardConfig.CheckText) + // Does the application want to validate the text itself? + if (_keyboardConfig.CheckText) + { + // The application needs to validate the response, so we + // submit it to the interactive output buffer, and poll it + // for validation. Once validated, the application will submit + // back a validation status, which is handled in OnInteractiveDataPushIn. + _state = SoftwareKeyboardState.ValidationPending; + + _interactiveSession.Push(BuildResponse(_textValue, true)); + } + else { // If the application doesn't need to validate the response, // we push the data to the non-interactive output buffer @@ -88,16 +114,6 @@ namespace Ryujinx.HLE.HOS.Applets AppletStateChanged?.Invoke(this, null); } - else - { - // The application needs to validate the response, so we - // submit it to the interactive output buffer, and poll it - // for validation. Once validated, the application will submit - // back a validation status, which is handled in OnInteractiveDataPushIn. - _state = SoftwareKeyboardState.ValidationPending; - - _interactiveSession.Push(BuildResponse(_textValue, true)); - } } private void OnInteractiveData(object sender, EventArgs e) @@ -136,12 +152,12 @@ namespace Ryujinx.HLE.HOS.Applets private byte[] BuildResponse(string text, bool interactive) { - int bufferSize = !interactive ? STANDARD_BUFFER_SIZE : INTERACTIVE_BUFFER_SIZE; + int bufferSize = interactive ? InteractiveBufferSize : StandardBufferSize; using (MemoryStream stream = new MemoryStream(new byte[bufferSize])) using (BinaryWriter writer = new BinaryWriter(stream)) { - byte[] output = Encoding.Unicode.GetBytes(text); + byte[] output = _encoding.GetBytes(text); if (!interactive) { diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs index 183da774ce..fd462382bc 100644 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs @@ -2,32 +2,137 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { - // TODO(jduncanator): Define all fields - [StructLayout(LayoutKind.Explicit)] + /// + /// A structure that defines the configuration options of the software keyboard. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct SoftwareKeyboardConfig { + private const int SubmitTextLength = 8; + private const int HeaderTextLength = 64; + private const int SubtitleTextLength = 128; + private const int GuideTextLength = 256; + /// /// Type of keyboard. /// - [FieldOffset(0x0)] - public SoftwareKeyboardType Type; + public KeyboardMode Mode; /// - /// When non-zero, specifies the max string length. When the input is too long, swkbd will stop accepting more input until text is deleted via the B button (Backspace). + /// The string displayed in the Submit button. /// - [FieldOffset(0x3AC)] - public uint StringLengthMax; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubmitTextLength + 1)] + public string SubmitText; /// - /// When non-zero, specifies the max string length. When the input is too long, swkbd will display an icon and disable the ok-button. + /// The character displayed in the left button of the numeric keyboard. + /// This is ignored when Mode is not set to NumbersOnly. /// - [FieldOffset(0x3B0)] - public uint StringLengthMaxExtended; + public char LeftOptionalSymbolKey; /// - /// When set, the application will validate the entered text whilst the swkbd is still on screen. + /// The character displayed in the right button of the numeric keyboard. + /// This is ignored when Mode is not set to NumbersOnly. /// - [FieldOffset(0x3D0), MarshalAs(UnmanagedType.I1)] + public char RightOptionalSymbolKey; + + /// + /// When set, predictive typing is enabled making use of the system dictionary, + /// and any custom user dictionary. + /// + [MarshalAs(UnmanagedType.I1)] + public bool PredictionEnabled; + + /// + /// Specifies prohibited characters that cannot be input into the text entry area. + /// + public InvalidCharFlags InvalidCharFlag; + + /// + /// The initial position of the text cursor displayed in the text entry area. + /// + public InitialCursorPosition InitialCursorPosition; + + /// + /// The string displayed in the header area of the keyboard. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = HeaderTextLength + 1)] + public string HeaderText; + + /// + /// The string displayed in the subtitle area of the keyboard. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubtitleTextLength + 1)] + public string SubtitleText; + + /// + /// The placeholder string displayed in the text entry area when no text is entered. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = GuideTextLength + 1)] + public string GuideText; + + /// + /// When non-zero, specifies the maximum allowed length of the string entered into the text entry area. + /// + public int StringLengthMax; + + /// + /// When non-zero, specifies the minimum allowed length of the string entered into the text entry area. + /// + public int StringLengthMin; + + /// + /// When enabled, hides input characters as dots in the text entry area. + /// + public PasswordMode PasswordMode; + + /// + /// Specifies whether the text entry area is displayed as a single-line entry, or a multi-line entry field. + /// + public InputFormMode InputFormMode; + + /// + /// When set, enables or disables the return key. This value is ignored when single-line entry is specified as the InputFormMode. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseNewLine; + + /// + /// When set, the software keyboard will return a UTF-8 encoded string, rather than UTF-16. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseUtf8; + + /// + /// When set, the software keyboard will blur the game application rendered behind the keyboard. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseBlurBackground; + + /// + /// Offset into the work buffer of the initial text when the keyboard is first displayed. + /// + public int InitialStringOffset; + + /// + /// Length of the initial text. + /// + public int InitialStringLength; + + /// + /// Offset into the work buffer of the custom user dictionary. + /// + public int CustomDictionaryOffset; + + /// + /// Number of entries in the custom user dictionary. + /// + public int CustomDictionaryCount; + + /// + /// When set, the text entered will be validated on the application side after the keyboard has been submitted. + /// + [MarshalAs(UnmanagedType.I1)] public bool CheckText; } } diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs index 42a2831ec9..0f66fc9bac 100644 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs @@ -1,6 +1,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { - internal enum SoftwareKeyboardState + /// + /// Identifies the software keyboard state. + /// + enum SoftwareKeyboardState { /// /// swkbd is uninitialized. diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs deleted file mode 100644 index 4875da8098..0000000000 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard -{ - internal enum SoftwareKeyboardType : uint - { - /// - /// Normal keyboard. - /// - Default = 0, - - /// - /// Number pad. The buttons at the bottom left/right are only available when they're set in the config by leftButtonText / rightButtonText. - /// - NumbersOnly = 1, - - /// - /// QWERTY (and variants) keyboard only. - /// - LettersOnly = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Lm/LogService/ILogger.cs b/Ryujinx.HLE/HOS/Services/Lm/LogService/ILogger.cs index 357a13324a..a269312ebe 100644 --- a/Ryujinx.HLE/HOS/Services/Lm/LogService/ILogger.cs +++ b/Ryujinx.HLE/HOS/Services/Lm/LogService/ILogger.cs @@ -8,6 +8,26 @@ namespace Ryujinx.HLE.HOS.Services.Lm.LogService { public ILogger() { } + private static int ReadEncodedInt(BinaryReader reader) + { + int result = 0; + int position = 0; + + byte encoded; + + do + { + encoded = reader.ReadByte(); + + result += (encoded & 0x7F) << (7 * position); + + position++; + + } while ((encoded & 0x80) != 0); + + return result; + } + [Command(0)] // Log(buffer) public ResultCode Log(ServiceCtx context) @@ -34,8 +54,8 @@ namespace Ryujinx.HLE.HOS.Services.Lm.LogService while (ms.Position < ms.Length) { - byte type = reader.ReadByte(); - byte size = reader.ReadByte(); + int type = ReadEncodedInt(reader); + int size = ReadEncodedInt(reader); LmLogField field = (LmLogField)type; 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 fe3841e362..d1ca3d1f4b 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/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 1280576364..b2b6cc7350 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -1,5 +1,6 @@ using Gtk; using Ryujinx.Common.Logging; +using Ryujinx.Configuration; using Ryujinx.Profiler; using Ryujinx.Ui; using System; @@ -16,9 +17,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(); @@ -42,23 +66,6 @@ namespace Ryujinx Application.Run(); } - private static void CurrentDomain_ProcessExit(object sender, EventArgs e) - { - 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) - { - Logger.Shutdown(); - } - } - private static void Glib_UnhandledException(GLib.UnhandledExceptionArgs e) { Exception exception = e.ExceptionObject as Exception; 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 63c78684a6..e5dbb3266f 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -1,20 +1,19 @@ -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; @@ -38,24 +37,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; @@ -72,6 +55,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 @@ -86,9 +70,11 @@ namespace Ryujinx.Ui DeleteEvent += Window_Close; ApplicationLibrary.ApplicationAdded += Application_Added; + + _gameTable.ButtonReleaseEvent += Row_Clicked; bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded); - if (!continueWithStartup) + if (!continueWithStartup) { End(); } @@ -97,7 +83,8 @@ namespace Ryujinx.Ui _audioOut = InitializeAudioEngine(); - _device = new HLE.Switch(_renderer, _audioOut); + // TODO: Initialization and dispose of HLE.Switch when starting/stoping emulation. + _device = InitializeSwitchInstance(); if (migrationNeeded) { @@ -111,56 +98,34 @@ namespace Ryujinx.Ui _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); @@ -174,22 +139,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}\"."); } } @@ -203,39 +168,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() @@ -249,7 +213,7 @@ namespace Ryujinx.Ui _tableStore.Clear(); - await Task.Run(() => ApplicationLibrary.LoadApplications(SwitchSettings.SwitchConfig.GameDirs, + await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage, _device.System.FsClient, _device.FileSystem)); @@ -266,6 +230,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"); @@ -331,40 +298,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"); @@ -402,8 +336,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(); @@ -462,7 +396,6 @@ namespace Ryujinx.Ui Profile.FinishProfiling(); _device?.Dispose(); _audioOut?.Dispose(); - DiscordClient?.Dispose(); Logger.Shutdown(); Environment.Exit(0); } @@ -488,24 +421,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; }); } @@ -544,12 +477,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); @@ -625,7 +571,7 @@ namespace Ryujinx.Ui private void Settings_Pressed(object sender, EventArgs args) { - SwitchSettings settingsWin = new SwitchSettings(_device); + SwitchSettings settingsWin = new SwitchSettings(); settingsWin.Show(); } @@ -658,121 +604,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(); } @@ -890,5 +796,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 6fdb853ad1..364a6d1d6e 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -346,7 +346,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..8bd164d81f 100644 --- a/Ryujinx/Ui/SwitchSettings.cs +++ b/Ryujinx/Ui/SwitchSettings.cs @@ -1,7 +1,7 @@ using Gtk; -using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Input; -using Ryujinx.Ui.Input; +using Ryujinx.Configuration; +using Ryujinx.Configuration.Hid; +using Ryujinx.Configuration.System; using System; using System.Collections.Generic; using System.IO; @@ -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();