Merge master into libhac-save

This commit is contained in:
Alex Barney 2019-12-23 19:16:57 -07:00
commit c18a7fe8bd
66 changed files with 2566 additions and 954 deletions

27
.github/workflows/build.yml vendored Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Type, MakeOp> _opActivators;
@ -36,10 +41,17 @@ namespace ARMeilleure.Decoders
Dictionary<ulong, Block> visited = new Dictionary<ulong, Block>();
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

View file

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

View file

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

View file

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

View file

@ -268,7 +268,7 @@ namespace ARMeilleure.Instructions
{
if (setCarry)
{
SetFlag(context, PState.CFlag, Const(0));;
SetFlag(context, PState.CFlag, Const(0));
}
return Const(0);

View file

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

View file

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

View file

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

View file

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

View file

@ -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();

View file

@ -8,6 +8,10 @@ namespace ARMeilleure.IntermediateRepresentation
X86Addss,
X86Andnpd,
X86Andnps,
X86Andpd,
X86Andps,
X86Blendvpd,
X86Blendvps,
X86Cmppd,
X86Cmpps,
X86Cmpsd,

View file

@ -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
{
/// <summary>
/// The default configuration instance
/// </summary>
public static Configuration Instance { get; private set; }
public int Version { get; set; }
/// <summary>
/// Dumps shaders in this local directory
@ -79,7 +71,7 @@ namespace Ryujinx
/// <summary>
/// Change System Language
/// </summary>
public SystemLanguage SystemLanguage { get; set; }
public Language SystemLanguage { get; set; }
/// <summary>
/// Enables or disables Docked Mode
@ -119,7 +111,7 @@ namespace Ryujinx
/// <summary>
/// The primary controller's type
/// </summary>
public ControllerStatus ControllerType { get; set; }
public ControllerType ControllerType { get; set; }
/// <summary>
/// Used to toggle columns in the GUI
@ -154,13 +146,13 @@ namespace Ryujinx
/// <summary>
/// Controller control bindings
/// </summary>
public Ui.Input.NpadController JoystickControls { get; private set; }
public NpadController JoystickControls { get; set; }
/// <summary>
/// Loads a configuration file from disk
/// </summary>
/// <param name="path">The path to the JSON configuration file</param>
public static void Load(string path)
public static ConfigurationFileFormat Load(string path)
{
var resolver = CompositeResolver.Create(
new[] { new ConfigurationEnumFormatter<Key>() },
@ -169,24 +161,7 @@ namespace Ryujinx
using (Stream stream = File.OpenRead(path))
{
Instance = JsonSerializer.Deserialize<Configuration>(stream, resolver);
}
}
/// <summary>
/// Loads a configuration file asynchronously from disk
/// </summary>
/// <param name="path">The path to the JSON configuration file</param>
public static async Task LoadAsync(string path)
{
IJsonFormatterResolver resolver = CompositeResolver.Create(
new[] { new ConfigurationEnumFormatter<Key>() },
new[] { StandardResolver.AllowPrivateSnakeCase }
);
using (Stream stream = File.OpenRead(path))
{
Instance = await JsonSerializer.DeserializeAsync<Configuration>(stream, resolver);
return JsonSerializer.Deserialize<ConfigurationFileFormat>(stream, resolver);
}
}
@ -194,108 +169,17 @@ namespace Ryujinx
/// Save a configuration file to disk
/// </summary>
/// <param name="path">The path to the JSON configuration file</param>
public static void SaveConfig(Configuration config, string path)
public void SaveConfig(string path)
{
IJsonFormatterResolver resolver = CompositeResolver.Create(
new[] { new ConfigurationEnumFormatter<Key>() },
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());
}
/// <summary>
/// Configures a <see cref="Switch"/> instance
/// </summary>
/// <param name="device">The instance to configure</param>
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<LogClass>())
{
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<T> : IJsonFormatter<T>
where T : struct
{

View file

@ -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
{
/// <summary>
/// UI configuration section
/// </summary>
public class UiSection
{
public class Columns
{
public ReactiveObject<bool> FavColumn { get; private set; }
public ReactiveObject<bool> IconColumn { get; private set; }
public ReactiveObject<bool> AppColumn { get; private set; }
public ReactiveObject<bool> DevColumn { get; private set; }
public ReactiveObject<bool> VersionColumn { get; private set; }
public ReactiveObject<bool> TimePlayedColumn { get; private set; }
public ReactiveObject<bool> LastPlayedColumn { get; private set; }
public ReactiveObject<bool> FileExtColumn { get; private set; }
public ReactiveObject<bool> FileSizeColumn { get; private set; }
public ReactiveObject<bool> PathColumn { get; private set; }
public Columns()
{
FavColumn = new ReactiveObject<bool>();
IconColumn = new ReactiveObject<bool>();
AppColumn = new ReactiveObject<bool>();
DevColumn = new ReactiveObject<bool>();
VersionColumn = new ReactiveObject<bool>();
TimePlayedColumn = new ReactiveObject<bool>();
LastPlayedColumn = new ReactiveObject<bool>();
FileExtColumn = new ReactiveObject<bool>();
FileSizeColumn = new ReactiveObject<bool>();
PathColumn = new ReactiveObject<bool>();
}
}
/// <summary>
/// Used to toggle columns in the GUI
/// </summary>
public Columns GuiColumns { get; private set; }
/// <summary>
/// A list of directories containing games to be used to load games into the games list
/// </summary>
public ReactiveObject<List<string>> GameDirs { get; private set; }
/// <summary>
/// Enable or disable custom themes in the GUI
/// </summary>
public ReactiveObject<bool> EnableCustomTheme { get; private set; }
/// <summary>
/// Path to custom GUI theme
/// </summary>
public ReactiveObject<string> CustomThemePath { get; private set; }
public UiSection()
{
GuiColumns = new Columns();
GameDirs = new ReactiveObject<List<string>>();
EnableCustomTheme = new ReactiveObject<bool>();
CustomThemePath = new ReactiveObject<string>();
}
}
/// <summary>
/// Logger configuration section
/// </summary>
public class LoggerSection
{
/// <summary>
/// Enables printing debug log messages
/// </summary>
public ReactiveObject<bool> EnableDebug { get; private set; }
/// <summary>
/// Enables printing stub log messages
/// </summary>
public ReactiveObject<bool> EnableStub { get; private set; }
/// <summary>
/// Enables printing info log messages
/// </summary>
public ReactiveObject<bool> EnableInfo { get; private set; }
/// <summary>
/// Enables printing warning log messages
/// </summary>
public ReactiveObject<bool> EnableWarn { get; private set; }
/// <summary>
/// Enables printing error log messages
/// </summary>
public ReactiveObject<bool> EnableError { get; private set; }
/// <summary>
/// Enables printing guest log messages
/// </summary>
public ReactiveObject<bool> EnableGuest { get; private set; }
/// <summary>
/// Enables printing FS access log messages
/// </summary>
public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
/// <summary>
/// Controls which log messages are written to the log targets
/// </summary>
public ReactiveObject<LogClass[]> FilteredClasses { get; private set; }
/// <summary>
/// Enables or disables logging to a file on disk
/// </summary>
public ReactiveObject<bool> EnableFileLog { get; private set; }
public LoggerSection()
{
EnableDebug = new ReactiveObject<bool>();
EnableStub = new ReactiveObject<bool>();
EnableInfo = new ReactiveObject<bool>();
EnableWarn = new ReactiveObject<bool>();
EnableError = new ReactiveObject<bool>();
EnableGuest = new ReactiveObject<bool>();
EnableFsAccessLog = new ReactiveObject<bool>();
FilteredClasses = new ReactiveObject<LogClass[]>();
EnableFileLog = new ReactiveObject<bool>();
}
}
/// <summary>
/// System configuration section
/// </summary>
public class SystemSection
{
/// <summary>
/// Change System Language
/// </summary>
public ReactiveObject<Language> Language { get; private set; }
/// <summary>
/// Enables or disables Docked Mode
/// </summary>
public ReactiveObject<bool> EnableDockedMode { get; private set; }
/// <summary>
/// Enables or disables multi-core scheduling of threads
/// </summary>
public ReactiveObject<bool> EnableMulticoreScheduling { get; private set; }
/// <summary>
/// Enables integrity checks on Game content files
/// </summary>
public ReactiveObject<bool> EnableFsIntegrityChecks { get; private set; }
/// <summary>
/// Enables FS access log output to the console. Possible modes are 0-3
/// </summary>
public ReactiveObject<int> FsGlobalAccessLogMode { get; private set; }
/// <summary>
/// Enable or disable ignoring missing services
/// </summary>
public ReactiveObject<bool> IgnoreMissingServices { get; private set; }
public SystemSection()
{
Language = new ReactiveObject<Language>();
EnableDockedMode = new ReactiveObject<bool>();
EnableMulticoreScheduling = new ReactiveObject<bool>();
EnableFsIntegrityChecks = new ReactiveObject<bool>();
FsGlobalAccessLogMode = new ReactiveObject<int>();
IgnoreMissingServices = new ReactiveObject<bool>();
}
}
/// <summary>
/// Hid configuration section
/// </summary>
public class HidSection
{
/// <summary>
/// The primary controller's type
/// </summary>
public ReactiveObject<ControllerType> ControllerType { get; private set; }
/// <summary>
/// Enable or disable keyboard support (Independent from controllers binding)
/// </summary>
public ReactiveObject<bool> EnableKeyboard { get; private set; }
/// <summary>
/// Keyboard control bindings
/// </summary>
public ReactiveObject<NpadKeyboard> KeyboardControls { get; private set; }
/// <summary>
/// Controller control bindings
/// </summary>
public ReactiveObject<NpadController> JoystickControls { get; private set; }
public HidSection()
{
ControllerType = new ReactiveObject<ControllerType>();
EnableKeyboard = new ReactiveObject<bool>();
KeyboardControls = new ReactiveObject<NpadKeyboard>();
JoystickControls = new ReactiveObject<NpadController>();
}
}
/// <summary>
/// Graphics configuration section
/// </summary>
public class GraphicsSection
{
/// <summary>
/// Dumps shaders in this local directory
/// </summary>
public ReactiveObject<string> ShadersDumpPath { get; private set; }
/// <summary>
/// Enables or disables Vertical Sync
/// </summary>
public ReactiveObject<bool> EnableVsync { get; private set; }
public GraphicsSection()
{
ShadersDumpPath = new ReactiveObject<string>();
EnableVsync = new ReactiveObject<bool>();
}
}
/// <summary>
/// The default configuration instance
/// </summary>
public static ConfigurationState Instance { get; private set; }
/// <summary>
/// The Ui section
/// </summary>
public UiSection Ui { get; private set; }
/// <summary>
/// The Logger section
/// </summary>
public LoggerSection Logger { get; private set; }
/// <summary>
/// The System section
/// </summary>
public SystemSection System { get; private set; }
/// <summary>
/// The Graphics section
/// </summary>
public GraphicsSection Graphics { get; private set; }
/// <summary>
/// The Hid section
/// </summary>
public HidSection Hid { get; private set; }
/// <summary>
/// Enables or disables Discord Rich Presence
/// </summary>
public ReactiveObject<bool> EnableDiscordIntegration { get; private set; }
private ConfigurationState()
{
Ui = new UiSection();
Logger = new LoggerSection();
System = new SystemSection();
Graphics = new GraphicsSection();
Hid = new HidSection();
EnableDiscordIntegration = new ReactiveObject<bool>();
}
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<string>();
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();
}
}
}

View file

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

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Configuration.Hid
{
public enum ControllerType
{
ProController,
Handheld,
NpadPair,
NpadLeft,
NpadRight
}
}

View file

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

View file

@ -0,0 +1,7 @@
namespace Ryujinx.Configuration.Hid
{
public struct KeyboardHotkeys
{
public Key ToggleVsync;
}
}

View file

@ -0,0 +1,35 @@
namespace Ryujinx.Common.Configuration.Hid
{
public class NpadController
{
/// <summary>
/// Enables or disables controller support
/// </summary>
public bool Enabled;
/// <summary>
/// Controller Device Index
/// </summary>
public int Index;
/// <summary>
/// Controller Analog Stick Deadzone
/// </summary>
public float Deadzone;
/// <summary>
/// Controller Trigger Threshold
/// </summary>
public float TriggerThreshold;
/// <summary>
/// Left JoyCon Controller Bindings
/// </summary>
public NpadControllerLeft LeftJoycon;
/// <summary>
/// Right JoyCon Controller Bindings
/// </summary>
public NpadControllerRight RightJoycon;
}
}

View file

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

View file

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

View file

@ -0,0 +1,20 @@
namespace Ryujinx.UI.Input
{
public class NpadKeyboard
{
/// <summary>
/// Left JoyCon Keyboard Bindings
/// </summary>
public Configuration.Hid.NpadKeyboardLeft LeftJoycon;
/// <summary>
/// Right JoyCon Keyboard Bindings
/// </summary>
public Configuration.Hid.NpadKeyboardRight RightJoycon;
/// <summary>
/// Hotkey Keyboard Bindings
/// </summary>
public Configuration.Hid.KeyboardHotkeys Hotkeys;
}
}

View file

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

View file

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

View file

@ -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<bool> e)
{
Logger.SetEnable(LogLevel.Debug, e.NewValue);
}
private static void ReloadEnableStub(object sender, ReactiveEventArgs<bool> e)
{
Logger.SetEnable(LogLevel.Stub, e.NewValue);
}
private static void ReloadEnableInfo(object sender, ReactiveEventArgs<bool> e)
{
Logger.SetEnable(LogLevel.Info, e.NewValue);
}
private static void ReloadEnableWarning(object sender, ReactiveEventArgs<bool> e)
{
Logger.SetEnable(LogLevel.Warning, e.NewValue);
}
private static void ReloadEnableError(object sender, ReactiveEventArgs<bool> e)
{
Logger.SetEnable(LogLevel.Error, e.NewValue);
}
private static void ReloadEnableGuest(object sender, ReactiveEventArgs<bool> e)
{
Logger.SetEnable(LogLevel.Guest, e.NewValue);
}
private static void ReloadEnableFsAccessLog(object sender, ReactiveEventArgs<bool> e)
{
Logger.SetEnable(LogLevel.AccessLog, e.NewValue);
}
private static void ReloadFilteredClasses(object sender, ReactiveEventArgs<LogClass[]> e)
{
bool noFilter = e.NewValue.Length == 0;
foreach (var logClass in EnumExtensions.GetValues<LogClass>())
{
Logger.SetEnable(logClass, noFilter);
}
foreach (var logClass in e.NewValue)
{
Logger.SetEnable(logClass, true);
}
}
private static void ReloadFileLogger(object sender, ReactiveEventArgs<bool> 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();
}
}
}
}

View file

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

View file

@ -1,4 +1,4 @@
namespace Ryujinx.Ui
namespace Ryujinx.Configuration.Ui
{
public struct GuiColumns
{

View file

@ -37,6 +37,12 @@ namespace Ryujinx.Common.Logging
m_LogTargets = new List<ILogTarget>();
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;

View file

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

View file

@ -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<LogLevel, ConsoleColor> {
@ -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)

View file

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

View file

@ -5,5 +5,7 @@ namespace Ryujinx.Common.Logging
public interface ILogTarget : IDisposable
{
void Log(object sender, LogEventArgs args);
string Name { get; }
}
}

View file

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

View file

@ -0,0 +1,57 @@
using System;
using System.Threading;
namespace Ryujinx.Common
{
public class ReactiveObject<T>
{
private ReaderWriterLock _readerWriterLock = new ReaderWriterLock();
private T _value;
public event EventHandler<ReactiveEventArgs<T>> 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<T>(oldValue, value));
}
}
}
public static implicit operator T(ReactiveObject<T> obj)
{
return obj.Value;
}
}
public class ReactiveEventArgs<T>
{
public T OldValue { get; }
public T NewValue { get; }
public ReactiveEventArgs(T oldValue, T newValue)
{
OldValue = oldValue;
NewValue = newValue;
}
}
}

View file

@ -27,6 +27,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonPrettyPrinter" Version="1.0.1.1" />
<PackageReference Include="Utf8Json" Version="1.3.7" />
</ItemGroup>

View file

@ -0,0 +1,18 @@
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
/// <summary>
/// Identifies the initial position of the cursor displayed in the area.
/// </summary>
enum InitialCursorPosition : uint
{
/// <summary>
/// Position the cursor at the beginning of the text
/// </summary>
Start,
/// <summary>
/// Position the cursor at the end of the text
/// </summary>
End
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
/// <summary>
/// Identifies the text entry mode.
/// </summary>
enum InputFormMode : uint
{
/// <summary>
/// Displays the text entry area as a single-line field.
/// </summary>
SingleLine,
/// <summary>
/// Displays the text entry area as a multi-line field.
/// </summary>
MultiLine
}
}

View file

@ -0,0 +1,56 @@
using System;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
/// <summary>
/// Identifies prohibited character sets.
/// </summary>
[Flags]
enum InvalidCharFlags : uint
{
/// <summary>
/// No characters are prohibited.
/// </summary>
None = 0 << 1,
/// <summary>
/// Prohibits spaces.
/// </summary>
Space = 1 << 1,
/// <summary>
/// Prohibits the at (@) symbol.
/// </summary>
AtSymbol = 1 << 2,
/// <summary>
/// Prohibits the percent (%) symbol.
/// </summary>
Percent = 1 << 3,
/// <summary>
/// Prohibits the forward slash (/) symbol.
/// </summary>
ForwardSlash = 1 << 4,
/// <summary>
/// Prohibits the backward slash (\) symbol.
/// </summary>
BackSlash = 1 << 5,
/// <summary>
/// Prohibits numbers.
/// </summary>
Numbers = 1 << 6,
/// <summary>
/// Prohibits characters outside of those allowed in download codes.
/// </summary>
DownloadCode = 1 << 7,
/// <summary>
/// Prohibits characters outside of those allowed in Mii Nicknames.
/// </summary>
Username = 1 << 8
}
}

View file

@ -0,0 +1,28 @@
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
/// <summary>
/// Identifies the variant of keyboard displayed on screen.
/// </summary>
enum KeyboardMode : uint
{
/// <summary>
/// A full alpha-numeric keyboard.
/// </summary>
Default,
/// <summary>
/// Number pad.
/// </summary>
NumbersOnly,
/// <summary>
/// QWERTY (and variants) keyboard only.
/// </summary>
LettersOnly,
/// <summary>
/// Unknown keyboard variant.
/// </summary>
Unknown
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
/// <summary>
/// Identifies the display mode of text in a password field.
/// </summary>
enum PasswordMode : uint
{
/// <summary>
/// Display input characters.
/// </summary>
Disabled,
/// <summary>
/// Hide input characters.
/// </summary>
Enabled
}
}

View file

@ -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<SoftwareKeyboardConfig>(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)
{

View file

@ -2,32 +2,137 @@
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
// TODO(jduncanator): Define all fields
[StructLayout(LayoutKind.Explicit)]
/// <summary>
/// A structure that defines the configuration options of the software keyboard.
/// </summary>
[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;
/// <summary>
/// Type of keyboard.
/// </summary>
[FieldOffset(0x0)]
public SoftwareKeyboardType Type;
public KeyboardMode Mode;
/// <summary>
/// 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.
/// </summary>
[FieldOffset(0x3AC)]
public uint StringLengthMax;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubmitTextLength + 1)]
public string SubmitText;
/// <summary>
/// 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.
/// </summary>
[FieldOffset(0x3B0)]
public uint StringLengthMaxExtended;
public char LeftOptionalSymbolKey;
/// <summary>
/// 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.
/// </summary>
[FieldOffset(0x3D0), MarshalAs(UnmanagedType.I1)]
public char RightOptionalSymbolKey;
/// <summary>
/// When set, predictive typing is enabled making use of the system dictionary,
/// and any custom user dictionary.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool PredictionEnabled;
/// <summary>
/// Specifies prohibited characters that cannot be input into the text entry area.
/// </summary>
public InvalidCharFlags InvalidCharFlag;
/// <summary>
/// The initial position of the text cursor displayed in the text entry area.
/// </summary>
public InitialCursorPosition InitialCursorPosition;
/// <summary>
/// The string displayed in the header area of the keyboard.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = HeaderTextLength + 1)]
public string HeaderText;
/// <summary>
/// The string displayed in the subtitle area of the keyboard.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubtitleTextLength + 1)]
public string SubtitleText;
/// <summary>
/// The placeholder string displayed in the text entry area when no text is entered.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = GuideTextLength + 1)]
public string GuideText;
/// <summary>
/// When non-zero, specifies the maximum allowed length of the string entered into the text entry area.
/// </summary>
public int StringLengthMax;
/// <summary>
/// When non-zero, specifies the minimum allowed length of the string entered into the text entry area.
/// </summary>
public int StringLengthMin;
/// <summary>
/// When enabled, hides input characters as dots in the text entry area.
/// </summary>
public PasswordMode PasswordMode;
/// <summary>
/// Specifies whether the text entry area is displayed as a single-line entry, or a multi-line entry field.
/// </summary>
public InputFormMode InputFormMode;
/// <summary>
/// When set, enables or disables the return key. This value is ignored when single-line entry is specified as the InputFormMode.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool UseNewLine;
/// <summary>
/// When set, the software keyboard will return a UTF-8 encoded string, rather than UTF-16.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool UseUtf8;
/// <summary>
/// When set, the software keyboard will blur the game application rendered behind the keyboard.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool UseBlurBackground;
/// <summary>
/// Offset into the work buffer of the initial text when the keyboard is first displayed.
/// </summary>
public int InitialStringOffset;
/// <summary>
/// Length of the initial text.
/// </summary>
public int InitialStringLength;
/// <summary>
/// Offset into the work buffer of the custom user dictionary.
/// </summary>
public int CustomDictionaryOffset;
/// <summary>
/// Number of entries in the custom user dictionary.
/// </summary>
public int CustomDictionaryCount;
/// <summary>
/// When set, the text entered will be validated on the application side after the keyboard has been submitted.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool CheckText;
}
}

View file

@ -1,6 +1,9 @@
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
internal enum SoftwareKeyboardState
/// <summary>
/// Identifies the software keyboard state.
/// </summary>
enum SoftwareKeyboardState
{
/// <summary>
/// swkbd is uninitialized.

View file

@ -1,20 +0,0 @@
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
internal enum SoftwareKeyboardType : uint
{
/// <summary>
/// Normal keyboard.
/// </summary>
Default = 0,
/// <summary>
/// Number pad. The buttons at the bottom left/right are only available when they're set in the config by leftButtonText / rightButtonText.
/// </summary>
NumbersOnly = 1,
/// <summary>
/// QWERTY (and variants) keyboard only.
/// </summary>
LettersOnly = 2
}
}

View file

@ -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<unknown, 0x21>)
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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -43,6 +43,7 @@
<None Remove="Ui\assets\PatreonLogo.png" />
<None Remove="Ui\assets\Icon.png" />
<None Remove="Ui\assets\TwitterLogo.png" />
<None Remove="Ui\GameTableContextMenu.glade" />
<None Remove="Ui\MainWindow.glade" />
<None Remove="Ui\SwitchSettings.glade" />
</ItemGroup>
@ -63,6 +64,7 @@
<EmbeddedResource Include="Ui\assets\PatreonLogo.png" />
<EmbeddedResource Include="Ui\assets\Icon.png" />
<EmbeddedResource Include="Ui\assets\TwitterLogo.png" />
<EmbeddedResource Include="Ui\GameTableContextMenu.glade" />
<EmbeddedResource Include="Ui\MainWindow.glade" />
<EmbeddedResource Include="Ui\SwitchSettings.glade" />
</ItemGroup>
@ -71,7 +73,6 @@
<PackageReference Include="DiscordRichPresence" Version="1.0.121" />
<PackageReference Include="GtkSharp" Version="3.22.25.24" />
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="JsonPrettyPrinter" Version="1.0.1.1" />
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
</ItemGroup>

View file

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

View file

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

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkMenu" id="_contextMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="_openSaveDir">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Open the folder where saves for the application is loaded</property>
<property name="label" translatable="yes">Open Save Directory</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
</interface>

View file

@ -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
{
/// <summary>
/// Left JoyCon Keyboard Bindings
/// </summary>
public NpadKeyboardLeft LeftJoycon { get; set; }
/// <summary>
/// Right JoyCon Keyboard Bindings
/// </summary>
public NpadKeyboardRight RightJoycon { get; set; }
/// <summary>
/// Hotkey Keyboard Bindings
/// </summary>
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
{

View file

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

View file

@ -346,7 +346,7 @@
<property name="hover_selection">True</property>
<signal name="row-activated" handler="Row_Activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
<object class="GtkTreeSelection" id="_gameTableSelection"/>
</child>
</object>
</child>

View file

@ -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
{
/// <summary>
/// Enables or disables controller support
/// </summary>
public bool Enabled { get; private set; }
private InnerNpadController _inner;
/// <summary>
/// Controller Device Index
/// </summary>
public int Index { get; private set; }
/// <summary>
/// Controller Analog Stick Deadzone
/// </summary>
public float Deadzone { get; private set; }
/// <summary>
/// Controller Trigger Threshold
/// </summary>
public float TriggerThreshold { get; private set; }
/// <summary>
/// Left JoyCon Controller Bindings
/// </summary>
public NpadControllerLeft LeftJoycon { get; private set; }
/// <summary>
/// Right JoyCon Controller Bindings
/// </summary>
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)

View file

@ -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();