Compare commits

..

21 Commits

Author SHA1 Message Date
79becc4b78 Dynamically increase buffer size when resizing (#2861)
* Grow buffers by 1.5x of its size when resizing

* Further restrict the cases where the dynamic expansion is done
2022-03-15 03:33:53 +01:00
223172ac0b Ui: Add option to show/hide console window (Windows-only) (#3170)
* Ui: Add option to show/hide console window (Windows-only)

* Ui: Only display Show Console menu item on Windows

* ConsoleHelper: Handle NULL case

This will never happen

* Address nits

* Address comments

* Address comments 2
2022-03-15 02:35:41 +01:00
8c9633d72f Initialize indexed inputs used on next shader stage (#3198) 2022-03-14 20:02:50 -03:00
1f93fd52d9 Do not initialize geometry shader passthrough attributes (#3196) 2022-03-14 19:35:41 -03:00
aac7bbd378 olsc: Implement GetSaveDataBackupSetting (#3190)
* olsc: Implement GetSaveDataBackupSetting

This PR implement GetSaveDataBackupSetting of OLSC service which is now needed by ACNH 2.0.5. The game is playable as usual if you use the same user profile as the original save file (I don't know if it was the case before), everything is checked by RE.

* addresses gdkchan feedback
2022-03-12 18:38:49 +01:00
bed516bfda Implement rotate stick 90 degrees clockwise (#3084)
* Implement swapping sticks

* Rotate 90 degrees clockwise

Co-authored-by: matesic.darko@gmail.com <Darkman1979>
2022-03-12 18:23:48 +01:00
69b05f9918 Fix GetUserDisableCount NRE (#3187) 2022-03-12 18:12:12 +01:00
fb7c80e928 Limit number of events that can be retrieved from GetDisplayVSyncEvent (#3188)
* Limit number of events that can be retrieved from GetDisplayVSyncEvent

* Cleaning

* Rename openDisplayInfos -> openDisplays
2022-03-12 17:56:19 +01:00
bb2f9df0a1 KThread: Fix GetPsr mask (#3180)
* ExecutionContext: GetPstate / SetPstate

* Put it in NativeContext

* KThread: Fix GetPsr mask

* ExecutionContext: Turn methods into Pstate property

* Address nit
2022-03-11 03:16:32 +01:00
54bfaa125d amadeus: Fix wrong Span usage in CopyHistories (#3181)
Fix a copypasta from the original Amadeus PR causing invalid
CopyHistories output.

Also added a missing size check.

This fix a crash in Mononoke Slashdown
2022-03-07 09:49:29 +01:00
7af9fcbc06 T32: Implement Data Processing (Modified Immediate) instructions (#3178)
* T32: Implement Data Processing (Modified Immediate) instructions

* Update tests

* switch -> lookup table
2022-03-06 22:25:01 +01:00
ee174be57c Mod loading from atmosphere SD directories (#3176)
* initial sd support

* GUI option

* alignment

* review changes
2022-03-06 22:12:01 +01:00
0bcbe32367 Only initialize shader outputs that are actually used on the next stage (#3054)
* Only initialize shader outputs that are actually used on the next stage

* Shader cache version bump
2022-03-06 20:42:13 +01:00
b97ff4da5e A32: Fix ALU immediate instructions (#3179)
* Tests: Add A32 tests for immediate ADC/ADCS/RSC/RSCS/SBC/SBCS

* A32: Fix bug in ADC/ADCS/RSC/RSCS/SBC/SBCS

* CpuTestAluImm32: Add more opcodes

* Increment PTC version
2022-03-05 15:23:10 -03:00
747081d2c7 Decoders: Fix instruction lengths for 16-bit B instructions (#3177) 2022-03-05 16:20:24 +01:00
497199bb50 Decoder: Exit on trapping instructions, and resume execution at trapping instruction (#3153)
* Decoder: Exit on trapping instructions, and resume execution at trapping instruction

* Resume at trapping address

* remove mustExit
2022-03-04 23:16:58 +01:00
bd9ac0fdaa T32: Implement B, B.cond, BL, BLX (#3155)
* Decoders: Make IsThumb a function of OpCode32

* OpCode32: Fix GetPc

* T32: Implement B, B.cond, BL, BLX

* rm usings
2022-03-04 23:05:08 +01:00
ac21abbb9d Preparation for initial Flatpack and FlatHub integration (#3173)
* Preparation for initial Flatpack and FlatHub integration

This integrate some initial changes required for Flatpack and distribution from FlatHub.

Also added some resources that will be used for packaging on Linux.

* Address gdkchan comment
2022-03-04 18:03:16 +01:00
a3dd04deef Implement -p or --profile command line argument (#2947)
* Implement -p or --profile command line argument

* Put command line logic all in Program and reference it elsewhere

* Address feedback
2022-03-02 09:51:37 +01:00
3705c20668 Update LibHac to v0.16.0 (#3159) 2022-02-27 00:52:25 +01:00
7b35ebc64a T32: Implement ALU (shifted register) instructions (#3135)
* T32: Implement ADC, ADD, AND, BIC, CMN, CMP, EOR, MOV, MVN, ORN, ORR, RSB, SBC, SUB, TEQ, TST (shifted register)

* OpCodeTable: Sort T32 list

* Tests: Rename RandomTestCase to PrecomputedThumbTestCase

* T32: Tests for AluRsImm instructions

* fix nit

* fix nit 2
2022-02-22 19:11:28 -03:00
79 changed files with 2299 additions and 324 deletions

View File

@ -121,7 +121,7 @@ namespace ARMeilleure.Decoders
currBlock.Branch = GetBlock((ulong)op.Immediate);
}
if (!IsUnconditionalBranch(lastOp) || isCall)
if (isCall || !(IsUnconditionalBranch(lastOp) || IsTrap(lastOp)))
{
currBlock.Next = GetBlock(currBlock.EndAddress);
}
@ -263,6 +263,11 @@ namespace ARMeilleure.Decoders
// so we must consider such operations as a branch in potential aswell.
if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc)
{
if (opCode is OpCodeT32)
{
return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq &&
opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn;
}
return true;
}
@ -324,9 +329,13 @@ namespace ARMeilleure.Decoders
}
private static bool IsException(OpCode opCode)
{
return IsTrap(opCode) || opCode.Instruction.Name == InstName.Svc;
}
private static bool IsTrap(OpCode opCode)
{
return opCode.Instruction.Name == InstName.Brk ||
opCode.Instruction.Name == InstName.Svc ||
opCode.Instruction.Name == InstName.Trap ||
opCode.Instruction.Name == InstName.Und;
}

View File

@ -13,11 +13,25 @@ namespace ARMeilleure.Decoders
Cond = (Condition)((uint)opCode >> 28);
}
public bool IsThumb()
{
return this is OpCodeT16 || this is OpCodeT32;
}
public uint GetPc()
{
// Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline,
// the PC actually points 2 instructions ahead.
return (uint)Address + (uint)OpCodeSizeInBytes * 2;
if (IsThumb())
{
// PC is ahead by 4 in thumb mode whether or not the current instruction
// is 16 or 32 bit.
return (uint)Address + 4u;
}
else
{
return (uint)Address + 8u;
}
}
}
}

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16BImm11 : OpCode32, IOpCode32BImm
class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm
{
public long Immediate { get; }

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16BImm8 : OpCode32, IOpCode32BImm
class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm
{
public long Immediate { get; }

View File

@ -0,0 +1,14 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32 : OpCode32
{
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32(inst, address, opCode);
public OpCodeT32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Cond = Condition.Al;
OpCodeSizeInBytes = 4;
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32Alu : OpCodeT32, IOpCode32Alu
{
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Alu(inst, address, opCode);
public OpCodeT32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 8) & 0xf;
Rn = (opCode >> 16) & 0xf;
SetFlags = ((opCode >> 20) & 1) != 0;
}
}
}

View File

@ -0,0 +1,38 @@
using ARMeilleure.Common;
using System.Runtime.Intrinsics;
namespace ARMeilleure.Decoders
{
class OpCodeT32AluImm : OpCodeT32Alu, IOpCode32AluImm
{
public int Immediate { get; }
public bool IsRotated { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm(inst, address, opCode);
private static readonly Vector128<int> _factor = Vector128.Create(1, 0x00010001, 0x01000100, 0x01010101);
public OpCodeT32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
int imm8 = (opCode >> 0) & 0xff;
int imm3 = (opCode >> 12) & 7;
int imm1 = (opCode >> 26) & 1;
int imm12 = imm8 | (imm3 << 8) | (imm1 << 11);
if ((imm12 >> 10) == 0)
{
Immediate = imm8 * _factor.GetElement((imm12 >> 8) & 3);
IsRotated = false;
}
else
{
int shift = imm12 >> 7;
Immediate = BitUtils.RotateRight(0x80 | (imm12 & 0x7f), shift, 32);
IsRotated = shift != 0;
}
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32AluRsImm : OpCodeT32Alu, IOpCode32AluRsImm
{
public int Rm { get; }
public int Immediate { get; }
public ShiftType ShiftType { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluRsImm(inst, address, opCode);
public OpCodeT32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rm = (opCode >> 0) & 0xf;
Immediate = ((opCode >> 6) & 3) | ((opCode >> 10) & 0x1c);
ShiftType = (ShiftType)((opCode >> 4) & 3);
}
}
}

View File

@ -0,0 +1,29 @@
using ARMeilleure.Instructions;
namespace ARMeilleure.Decoders
{
class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm
{
public long Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode);
public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
uint pc = GetPc();
int imm11 = (opCode >> 0) & 0x7ff;
int j2 = (opCode >> 11) & 1;
int j1 = (opCode >> 13) & 1;
int imm6 = (opCode >> 16) & 0x3f;
int s = (opCode >> 26) & 1;
int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19);
imm32 = (imm32 << 13) >> 12;
Immediate = pc + imm32;
Cond = (Condition)((opCode >> 22) & 0xf);
}
}
}

View File

@ -0,0 +1,35 @@
using ARMeilleure.Instructions;
namespace ARMeilleure.Decoders
{
class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm
{
public long Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode);
public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
uint pc = GetPc();
if (inst.Name == InstName.Blx)
{
pc &= ~3u;
}
int imm11 = (opCode >> 0) & 0x7ff;
int j2 = (opCode >> 11) & 1;
int j1 = (opCode >> 13) & 1;
int imm10 = (opCode >> 16) & 0x3ff;
int s = (opCode >> 26) & 1;
int i1 = j1 ^ s ^ 1;
int i2 = j2 ^ s ^ 1;
int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23);
imm32 = (imm32 << 9) >> 8;
Immediate = pc + imm32;
}
}
}

View File

@ -1,6 +1,7 @@
using ARMeilleure.Instructions;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace ARMeilleure.Decoders
{
@ -972,8 +973,7 @@ namespace ARMeilleure.Decoders
SetA32("111100111x11<<10xxxx00011xx0xxxx", InstName.Vzip, InstEmit32.Vzip, OpCode32SimdCmpZ.Create);
#endregion
#region "OpCode Table (AArch32, T16/T32)"
// T16
#region "OpCode Table (AArch32, T16)"
SetT16("000<<xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftImm.Create);
SetT16("0001100xxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubReg.Create);
SetT16("0001101xxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT16AddSubReg.Create);
@ -1045,6 +1045,46 @@ namespace ARMeilleure.Decoders
SetT16("11100xxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT16BImm11.Create);
#endregion
#region "OpCode Table (AArch32, T32)"
// Base
SetT32("11101011010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluRsImm.Create);
SetT32("11110x01010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluImm.Create);
SetT32("11101011000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluRsImm.Create);
SetT32("11110x01000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluImm.Create);
SetT32("11101010000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluRsImm.Create);
SetT32("11110x00000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluImm.Create);
SetT32("11110x<<<xxxxxxx10x0xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm20.Create);
SetT32("11110xxxxxxxxxxx10x1xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm24.Create);
SetT32("11101010001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluRsImm.Create);
SetT32("11110x00001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluImm.Create);
SetT32("11110xxxxxxxxxxx11x1xxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCodeT32BImm24.Create);
SetT32("11110xxxxxxxxxxx11x0xxxxxxxxxxx0", InstName.Blx, InstEmit32.Blx, OpCodeT32BImm24.Create);
SetT32("111010110001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluRsImm.Create);
SetT32("11110x010001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluImm.Create);
SetT32("111010111011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluRsImm.Create);
SetT32("11110x011011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluImm.Create);
SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create);
SetT32("11110x00100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluImm.Create);
SetT32("11101010010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluRsImm.Create);
SetT32("11110x00010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluImm.Create);
SetT32("11101010011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluRsImm.Create);
SetT32("11110x00011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluImm.Create);
SetT32("11101010011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluRsImm.Create);
SetT32("11110x00011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluImm.Create);
SetT32("11101010010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluRsImm.Create);
SetT32("11110x00010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluImm.Create);
SetT32("11101011110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluRsImm.Create);
SetT32("11110x01110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluImm.Create);
SetT32("11101011011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluRsImm.Create);
SetT32("11110x01011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluImm.Create);
SetT32("11101011101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluRsImm.Create);
SetT32("11110x01101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluImm.Create);
SetT32("111010101001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluRsImm.Create);
SetT32("11110x001001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluImm.Create);
SetT32("111010100001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluRsImm.Create);
SetT32("11110x000001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluImm.Create);
#endregion
FillFastLookupTable(InstA32FastLookup, AllInstA32, ToFastLookupIndexA);
FillFastLookupTable(InstT32FastLookup, AllInstT32, ToFastLookupIndexT);
FillFastLookupTable(InstA64FastLookup, AllInstA64, ToFastLookupIndexA);
@ -1092,8 +1132,11 @@ namespace ARMeilleure.Decoders
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
{
encoding = encoding.Substring(16) + encoding.Substring(0, 16);
Set(encoding, AllInstT32, new InstDescriptor(name, emitter), makeOp);
string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
MakeOp reversedMakeOp =
(InstDescriptor inst, ulong address, int opCode)
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
Set(reversedEncoding, AllInstT32, new InstDescriptor(name, emitter), reversedMakeOp);
}
private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)

View File

@ -244,6 +244,23 @@ namespace ARMeilleure.Instructions
EmitAluStore(context, res);
}
public static void Orn(ArmEmitterContext context)
{
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
Operand n = GetAluN(context);
Operand m = GetAluM(context);
Operand res = context.BitwiseOr(n, context.BitwiseNot(m));
if (ShouldSetFlags(context))
{
EmitNZFlagsCheck(context, res);
}
EmitAluStore(context, res);
}
public static void Pkh(ArmEmitterContext context)
{
OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp;

View File

@ -128,7 +128,7 @@ namespace ARMeilleure.Instructions
{
Debug.Assert(value.Type == OperandType.I32);
if (IsThumb(context.CurrOp))
if (((OpCode32)context.CurrOp).IsThumb())
{
bool isReturn = IsA32Return(context);
if (!isReturn)
@ -197,7 +197,7 @@ namespace ARMeilleure.Instructions
// ARM32.
case IOpCode32AluImm op:
{
if (ShouldSetFlags(context) && op.IsRotated)
if (ShouldSetFlags(context) && op.IsRotated && setCarry)
{
SetFlag(context, PState.CFlag, Const((uint)op.Immediate >> 31));
}

View File

@ -9,18 +9,25 @@ namespace ARMeilleure.Instructions
{
public static void Brk(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.Break));
OpCodeException op = (OpCodeException)context.CurrOp;
string name = nameof(NativeInterface.Break);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
context.LoadFromContext();
context.Return(Const(op.Address));
}
public static void Svc(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
}
private static void EmitExceptionCall(ArmEmitterContext context, string name)
{
OpCodeException op = (OpCodeException)context.CurrOp;
string name = nameof(NativeInterface.SupervisorCall);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
@ -41,6 +48,8 @@ namespace ARMeilleure.Instructions
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.RawOpCode));
context.LoadFromContext();
context.Return(Const(op.Address));
}
}
}

View File

@ -9,19 +9,11 @@ namespace ARMeilleure.Instructions
static partial class InstEmit32
{
public static void Svc(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
}
public static void Trap(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.Break));
}
private static void EmitExceptionCall(ArmEmitterContext context, string name)
{
IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
string name = nameof(NativeInterface.SupervisorCall);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
@ -30,5 +22,20 @@ namespace ARMeilleure.Instructions
Translator.EmitSynchronization(context);
}
public static void Trap(ArmEmitterContext context)
{
IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
string name = nameof(NativeInterface.Break);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
context.LoadFromContext();
context.Return(Const(context.CurrOp.Address));
}
}
}

View File

@ -34,7 +34,7 @@ namespace ARMeilleure.Instructions
uint pc = op.GetPc();
bool isThumb = IsThumb(context.CurrOp);
bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
uint currentPc = isThumb
? pc | 1
@ -61,7 +61,7 @@ namespace ARMeilleure.Instructions
Operand addr = context.Copy(GetIntA32(context, op.Rm));
Operand bitOne = context.BitwiseAnd(addr, Const(1));
bool isThumb = IsThumb(context.CurrOp);
bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
uint currentPc = isThumb
? (pc - 2) | 1

View File

@ -10,11 +10,6 @@ namespace ARMeilleure.Instructions
{
static class InstEmitHelper
{
public static bool IsThumb(OpCode op)
{
return op is OpCodeT16;
}
public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type)
{
Operand value = GetIntOrZR(context, rm);

View File

@ -43,6 +43,12 @@ namespace ARMeilleure.State
public long TpidrEl0 { get; set; }
public long Tpidr { get; set; }
public uint Pstate
{
get => _nativeContext.GetPstate();
set => _nativeContext.SetPstate(value);
}
public FPCR Fpcr { get; set; }
public FPSR Fpsr { get; set; }
public FPCR StandardFpcrValue => (Fpcr & (FPCR.Ahp)) | FPCR.Dn | FPCR.Fz;

View File

@ -95,6 +95,25 @@ namespace ARMeilleure.State
GetStorage().Flags[(int)flag] = value ? 1u : 0u;
}
public unsafe uint GetPstate()
{
uint value = 0;
for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++)
{
value |= GetStorage().Flags[flag] != 0 ? 1u << flag : 0u;
}
return value;
}
public unsafe void SetPstate(uint value)
{
for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++)
{
uint bit = 1u << flag;
GetStorage().Flags[flag] = (value & bit) == bit ? 1u : 0u;
}
}
public unsafe bool GetFPStateFlag(FPState flag)
{
if ((uint)flag >= RegisterConsts.FpFlagsCount)

View File

@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 3138; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 3179; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";

View File

@ -149,11 +149,21 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
Span<byte> targetSpan = performanceOutput.Slice(nextOffset);
// NOTE: We check for the space for two headers for the final blank header.
int requiredSpace = Unsafe.SizeOf<THeader>() + Unsafe.SizeOf<TEntry>() * inputHeader.GetEntryCount()
+ Unsafe.SizeOf<TEntryDetail>() * inputHeader.GetEntryDetailCount()
+ Unsafe.SizeOf<THeader>();
if (targetSpan.Length < requiredSpace)
{
break;
}
ref THeader outputHeader = ref MemoryMarshal.Cast<byte, THeader>(targetSpan)[0];
nextOffset += Unsafe.SizeOf<THeader>();
Span<TEntry> outputEntries = MemoryMarshal.Cast<byte, TEntry>(targetSpan.Slice(nextOffset));
Span<TEntry> outputEntries = MemoryMarshal.Cast<byte, TEntry>(performanceOutput.Slice(nextOffset));
int totalProcessingTime = 0;
@ -175,7 +185,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
}
}
Span<TEntryDetail> outputEntriesDetail = MemoryMarshal.Cast<byte, TEntryDetail>(targetSpan.Slice(nextOffset));
Span<TEntryDetail> outputEntriesDetail = MemoryMarshal.Cast<byte, TEntryDetail>(performanceOutput.Slice(nextOffset));
int effectiveEntryDetailCount = 0;

View File

@ -34,6 +34,7 @@ namespace Ryujinx.Common.Configuration
private const string DefaultModsDir = "mods";
public static string CustomModsPath { get; set; }
public static string CustomSdModsPath {get; set; }
public static string CustomNandPath { get; set; } // TODO: Actually implement this into VFS
public static string CustomSdCardPath { get; set; } // TODO: Actually implement this into VFS
@ -84,6 +85,7 @@ namespace Ryujinx.Common.Configuration
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
}
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
}
}

View File

@ -5,6 +5,7 @@
public Stick Joystick { get; set; }
public bool InvertStickX { get; set; }
public bool InvertStickY { get; set; }
public bool Rotate90CW { get; set; }
public Button StickButton { get; set; }
}
}

View File

@ -1,10 +1,14 @@
using System.Reflection;
using Ryujinx.Common.Configuration;
using System;
using System.Reflection;
namespace Ryujinx.Common
{
// DO NOT EDIT, filled by CI
public static class ReleaseInformations
{
private const string FlatHubChannelOwner = "flathub";
public static string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
public static string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
public static string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
@ -19,6 +23,11 @@ namespace Ryujinx.Common
!ReleaseChannelRepo.StartsWith("%%");
}
public static bool IsFlatHubBuild()
{
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
}
public static string GetVersion()
{
if (IsValid())
@ -30,5 +39,15 @@ namespace Ryujinx.Common
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
}
}
public static string GetBaseApplicationDirectory()
{
if (IsFlatHubBuild())
{
return AppDataManager.BaseDirPath;
}
return AppDomain.CurrentDomain.BaseDirectory;
}
}
}

View File

@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private const ulong BufferAlignmentSize = 0x1000;
private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
private const ulong MaxDynamicGrowthSize = 0x100000;
private readonly GpuContext _context;
private readonly PhysicalMemory _physicalMemory;
@ -166,10 +168,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Otherwise, we must delete the overlapping buffers and create a bigger buffer
// that fits all the data we need. We also need to copy the contents from the
// old buffer(s) to the new buffer.
ulong endAddress = address + size;
if (_bufferOverlaps[0].Address > address || _bufferOverlaps[0].EndAddress < endAddress)
{
// Check if the following conditions are met:
// - We have a single overlap.
// - The overlap starts at or before the requested range. That is, the overlap happens at the end.
// - The size delta between the new, merged buffer and the old one is of at most 2 pages.
// In this case, we attempt to extend the buffer further than the requested range,
// this can potentially avoid future resizes if the application keeps using overlapping
// sequential memory.
// Allowing for 2 pages (rather than just one) is necessary to catch cases where the
// range crosses a page, and after alignment, ends having a size of 2 pages.
if (overlapsCount == 1 &&
address >= _bufferOverlaps[0].Address &&
endAddress - _bufferOverlaps[0].EndAddress <= BufferAlignmentSize * 2)
{
// Try to grow the buffer by 1.5x of its current size.
// This improves performance in the cases where the buffer is resized often by small amounts.
ulong existingSize = _bufferOverlaps[0].Size;
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
size = Math.Max(size, growthSize);
endAddress = address + size;
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
}
for (int index = 0; index < overlapsCount; index++)
{
Buffer buffer = _bufferOverlaps[index];
@ -183,7 +210,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
Buffer newBuffer = new Buffer(_context, _physicalMemory, address, endAddress - address, _bufferOverlaps.Take(overlapsCount));
ulong newSize = endAddress - address;
Buffer newBuffer = new Buffer(_context, _physicalMemory, address, newSize, _bufferOverlaps.Take(overlapsCount));
lock (_buffers)
{
@ -202,7 +231,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
buffer.DisposeData();
}
newBuffer.SynchronizeMemory(address, endAddress - address);
newBuffer.SynchronizeMemory(address, newSize);
// Existing buffers were modified, we need to rebind everything.
NotifyBuffersModified?.Invoke();

View File

@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Version of the codegen (to be changed when codegen or guest format change).
/// </summary>
private const ulong ShaderCodeGenVersion = 3132;
private const ulong ShaderCodeGenVersion = 3054;
// Progress reporting helpers
private volatile int _shaderCount;

View File

@ -308,7 +308,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
int attr = offset + elemIndex * 4;
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
{
int index = (attr - AttributeConsts.UserAttributeBase) / 16;
int userAttr = attr - AttributeConsts.UserAttributeBase;
int index = userAttr / 16;
if (isStore)
{
@ -316,7 +317,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
}
else
{
config.SetInputUserAttribute(index, perPatch);
config.SetInputUserAttribute(index, (userAttr >> 2) & 3, perPatch);
}
}

View File

@ -54,6 +54,11 @@ namespace Ryujinx.Graphics.Shader.Translation
private int _nextUsedInputAttributes;
private int _thisUsedInputAttributes;
public UInt128 NextInputAttributesComponents { get; private set; }
public UInt128 ThisInputAttributesComponents { get; private set; }
public UInt128 NextInputAttributesPerPatchComponents { get; private set; }
public UInt128 ThisInputAttributesPerPatchComponents { get; private set; }
private int _usedConstantBuffers;
private int _usedStorageBuffers;
private int _usedStorageBuffersWrite;
@ -227,11 +232,12 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedOutputAttributes |= 1 << index;
}
public void SetInputUserAttribute(int index, bool perPatch)
public void SetInputUserAttribute(int index, int component, bool perPatch)
{
if (perPatch)
{
UsedInputAttributesPerPatch |= 1 << index;
ThisInputAttributesPerPatchComponents |= UInt128.Pow2(index * 4 + component);
}
else
{
@ -239,6 +245,7 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedInputAttributes |= mask;
_thisUsedInputAttributes |= mask;
ThisInputAttributesComponents |= UInt128.Pow2(index * 4 + component);
}
}
@ -256,6 +263,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public void MergeFromtNextStage(ShaderConfig config)
{
NextInputAttributesComponents = config.ThisInputAttributesComponents;
NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents;
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
}
@ -319,6 +328,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public void SetAllInputUserAttributes()
{
UsedInputAttributes |= Constants.AllAttributesMask;
ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4);
}
public void SetAllOutputUserAttributes()

View File

@ -214,24 +214,31 @@ namespace Ryujinx.Graphics.Shader.Translation
InitializeOutput(context, AttributeConsts.PositionX, perPatch: false);
}
int usedAttributes = context.Config.UsedOutputAttributes;
while (usedAttributes != 0)
UInt128 usedAttributes = context.Config.NextInputAttributesComponents;
while (usedAttributes != UInt128.Zero)
{
int index = BitOperations.TrailingZeroCount(usedAttributes);
int index = usedAttributes.TrailingZeroCount();
int vecIndex = index / 4;
InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: false);
usedAttributes &= ~UInt128.Pow2(index);
usedAttributes &= ~(1 << index);
// We don't need to initialize passthrough attributes.
if ((context.Config.PassthroughAttributes & (1 << vecIndex)) != 0)
{
continue;
}
InitializeOutputComponent(context, AttributeConsts.UserAttributeBase + index * 4, perPatch: false);
}
int usedAttributesPerPatch = context.Config.UsedOutputAttributesPerPatch;
while (usedAttributesPerPatch != 0)
UInt128 usedAttributesPerPatch = context.Config.NextInputAttributesPerPatchComponents;
while (usedAttributesPerPatch != UInt128.Zero)
{
int index = BitOperations.TrailingZeroCount(usedAttributesPerPatch);
int index = usedAttributesPerPatch.TrailingZeroCount();
InitializeOutput(context, AttributeConsts.UserAttributeBase + index * 16, perPatch: true);
InitializeOutputComponent(context, AttributeConsts.UserAttributeBase + index * 4, perPatch: true);
usedAttributesPerPatch &= ~(1 << index);
usedAttributesPerPatch &= ~UInt128.Pow2(index);
}
if (config.NextUsesFixedFuncAttributes)
@ -260,6 +267,12 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
private static void InitializeOutputComponent(EmitterContext context, int attrOffset, bool perPatch)
{
int c = (attrOffset >> 2) & 3;
context.Copy(perPatch ? AttributePerPatch(attrOffset) : Attribute(attrOffset), ConstF(c == 3 ? 1f : 0f));
}
private static void EmitOps(EmitterContext context, Block block)
{
for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++)

View File

@ -0,0 +1,112 @@
using System;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation
{
struct UInt128 : IEquatable<UInt128>
{
public static UInt128 Zero => new UInt128() { _v0 = 0, _v1 = 0 };
private ulong _v0;
private ulong _v1;
public UInt128(ulong low, ulong high)
{
_v0 = low;
_v1 = high;
}
public int TrailingZeroCount()
{
int count = BitOperations.TrailingZeroCount(_v0);
if (count == 64)
{
count += BitOperations.TrailingZeroCount(_v1);
}
return count;
}
public static UInt128 Pow2(int x)
{
if (x >= 64)
{
return new UInt128(0, 1UL << (x - 64));
}
return new UInt128(1UL << x, 0);
}
public static UInt128 operator ~(UInt128 x)
{
return new UInt128(~x._v0, ~x._v1);
}
public static UInt128 operator &(UInt128 x, UInt128 y)
{
return new UInt128(x._v0 & y._v0, x._v1 & y._v1);
}
public static UInt128 operator |(UInt128 x, UInt128 y)
{
return new UInt128(x._v0 | y._v0, x._v1 | y._v1);
}
public static UInt128 operator <<(UInt128 x, int shift)
{
if (shift == 0)
{
return new UInt128(x._v0, x._v1);
}
else if (shift >= 64)
{
return new UInt128(0, x._v0 << (shift - 64));
}
ulong shiftOut = x._v0 >> (64 - shift);
return new UInt128(x._v0 << shift, (x._v1 << shift) | shiftOut);
}
public static UInt128 operator >>(UInt128 x, int shift)
{
if (shift == 0)
{
return new UInt128(x._v0, x._v1);
}
else if (shift >= 64)
{
return new UInt128(x._v1 >> (shift - 64), 0);
}
ulong shiftOut = x._v1 & ((1UL << shift) - 1);
return new UInt128((x._v0 >> shift) | (shiftOut << (64 - shift)), x._v1 >> shift);
}
public static bool operator ==(UInt128 x, UInt128 y)
{
return x.Equals(y);
}
public static bool operator !=(UInt128 x, UInt128 y)
{
return !x.Equals(y);
}
public override bool Equals(object obj)
{
return obj is UInt128 other && Equals(other);
}
public bool Equals(UInt128 other)
{
return _v0 == other._v0 && _v1 == other._v1;
}
public override int GetHashCode()
{
return HashCode.Combine(_v0, _v1);
}
}
}

View File

@ -84,7 +84,10 @@ namespace Ryujinx.HLE.HOS
MetaLoader metaData = ReadNpdm(codeFs);
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(new[] { TitleId }, _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath());
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
new[] { TitleId },
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
if (TitleId != 0)
{
@ -388,7 +391,10 @@ namespace Ryujinx.HLE.HOS
MetaLoader metaData = ReadNpdm(codeFs);
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath());
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId),
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
if (controlNca != null)
{
@ -481,14 +487,14 @@ namespace Ryujinx.HLE.HOS
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
{
titleName = controlData.Value.Titles[(int)device.System.State.DesiredTitleLanguage].Name.ToString();
titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
if (string.IsNullOrWhiteSpace(titleName))
{
titleName = controlData.Value.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
}
displayVersion = controlData.Value.DisplayVersion.ToString();
displayVersion = controlData.Value.DisplayVersionString.ToString();
}
}
else
@ -615,20 +621,20 @@ namespace Ryujinx.HLE.HOS
ref ApplicationControlProperty nacp = ref ControlData.Value;
programInfo.Name = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
if (string.IsNullOrWhiteSpace(programInfo.Name))
{
programInfo.Name = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
}
if (nacp.PresenceGroupId != 0)
{
programInfo.ProgramId = nacp.PresenceGroupId;
}
else if (nacp.SaveDataOwnerId.Value != 0)
else if (nacp.SaveDataOwnerId != 0)
{
programInfo.ProgramId = nacp.SaveDataOwnerId.Value;
programInfo.ProgramId = nacp.SaveDataOwnerId;
}
else if (nacp.AddOnContentBaseId != 0)
{
@ -776,14 +782,14 @@ namespace Ryujinx.HLE.HOS
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
control.UserAccountSaveDataSize = 0x4000;
control.UserAccountSaveDataJournalSize = 0x4000;
control.SaveDataOwnerId = applicationId;
control.SaveDataOwnerId = applicationId.Value;
Logger.Warning?.Print(LogClass.Application,
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
}
HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, ref control);
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
if (resultCode.IsFailure())
{
@ -792,7 +798,7 @@ namespace Ryujinx.HLE.HOS
return resultCode;
}
resultCode = EnsureApplicationSaveData(hos.Fs, out _, applicationId, ref control, ref user);
resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
if (resultCode.IsFailure())
{

View File

@ -466,7 +466,7 @@ namespace Ryujinx.HLE.HOS
AudioRendererManager.Dispose();
LibHacHorizonManager.AmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value);
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
KernelContext.Dispose();
}

View File

@ -751,7 +751,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
KThread currentThread = KernelStatic.GetCurrentThread();
if (currentThread.Owner != null &&
if (currentThread.Context.Running &&
currentThread.Owner != null &&
currentThread.GetUserDisableCount() != 0 &&
currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null)
{

View File

@ -658,10 +658,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private static uint GetPsr(ARMeilleure.State.ExecutionContext context)
{
return (context.GetPstateFlag(ARMeilleure.State.PState.NFlag) ? (1U << (int)ARMeilleure.State.PState.NFlag) : 0U) |
(context.GetPstateFlag(ARMeilleure.State.PState.ZFlag) ? (1U << (int)ARMeilleure.State.PState.ZFlag) : 0U) |
(context.GetPstateFlag(ARMeilleure.State.PState.CFlag) ? (1U << (int)ARMeilleure.State.PState.CFlag) : 0U) |
(context.GetPstateFlag(ARMeilleure.State.PState.VFlag) ? (1U << (int)ARMeilleure.State.PState.VFlag) : 0U);
return context.Pstate & 0xFF0FFE20;
}
private ThreadContext GetCurrentContext()
@ -1371,7 +1368,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
PreferredCore = _originalPreferredCore;
AffinityMask = _originalAffinityMask;
if (AffinityMask != affinityMask)
{
if ((AffinityMask & 1UL << ActiveCore) != 0)

View File

@ -24,6 +24,7 @@ namespace Ryujinx.HLE.HOS
public HorizonClient BcatClient { get; private set; }
public HorizonClient FsClient { get; private set; }
public HorizonClient NsClient { get; private set; }
public HorizonClient PmClient { get; private set; }
public HorizonClient SdbClient { get; private set; }
private SharedRef<LibHacIReader> _arpIReader;
@ -65,6 +66,7 @@ namespace Ryujinx.HLE.HOS
public void InitializeSystemClients()
{
PmClient = Server.CreatePrivilegedHorizonClient();
AccountClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Account, StorageId.BuiltInSystem), AccountFsPermissions);
AmClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Am, StorageId.BuiltInSystem), AmFsPermissions);
NsClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Ns, StorageId.BuiltInSystem), NsFsPermissions);

View File

@ -136,7 +136,8 @@ namespace Ryujinx.HLE.HOS
private static bool StrEquals(string s1, string s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
public string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
public string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
public string GetSdModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetSdModsPath());
private string EnsureBaseDirStructure(string modsBasePath)
{
@ -695,7 +696,7 @@ namespace Ryujinx.HLE.HOS
var buildIds = programs.Select(p => p switch
{
NsoExecutable nso => BitConverter.ToString(nso.BuildId.Bytes.ToArray()).Replace("-", "").TrimEnd('0'),
NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()).Replace("-", "").TrimEnd('0'),
NroExecutable nro => BitConverter.ToString(nro.Header.BuildId).Replace("-", "").TrimEnd('0'),
_ => string.Empty
}).ToList();

View File

@ -160,7 +160,7 @@ namespace Ryujinx.HLE.HOS
var buildIds = executables.Select(e => (e switch
{
NsoExecutable nso => BitConverter.ToString(nso.BuildId.Bytes.ToArray()),
NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()),
NroExecutable nro => BitConverter.ToString(nro.Header.BuildId),
_ => ""
}).Replace("-", "").ToUpper());

View File

@ -3,6 +3,7 @@ using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -25,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
public UserProfile LastOpenedUser { get; private set; }
public AccountManager(HorizonClient horizonClient)
public AccountManager(HorizonClient horizonClient, string initialProfileName = null)
{
_horizonClient = horizonClient;
@ -43,7 +44,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
}
else
{
OpenUser(_accountSaveDataManager.LastOpened);
UserId commandLineUserProfileOverride = default;
if (!string.IsNullOrEmpty(initialProfileName))
{
commandLineUserProfileOverride = _profiles.Values.FirstOrDefault(x => x.Name == initialProfileName)?.UserId ?? default;
if (commandLineUserProfileOverride.IsNull)
Logger.Warning?.Print(LogClass.Application, $"The command line specified profile named '{initialProfileName}' was not found");
}
OpenUser(commandLineUserProfileOverride.IsNull ? _accountSaveDataManager.LastOpened : commandLineUserProfileOverride);
}
}
@ -168,8 +176,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
private void DeleteSaveData(UserId userId)
{
SaveDataFilter saveDataFilter = new SaveDataFilter();
saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low));
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: default,
new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low), saveDataId: default, index: default);
using var saveDataIterator = new UniqueRef<SaveDataIterator>();

View File

@ -169,7 +169,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
// But since we use LibHac and we load one Application at a time, it's not necessary.
context.ResponseData.Write(context.Device.Application.ControlData.Value.UserAccountSwitchLock);
context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock);
Logger.Stub?.PrintStub(LogClass.ServiceAcc);

View File

@ -131,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
}
HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
Result result = EnsureApplicationSaveData(hos.Fs, out long requiredSize, applicationId, ref control, ref userId);
Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId);
context.ResponseData.Write(requiredSize);
@ -148,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// TODO: When above calls are implemented, switch to using ns:am
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguages;
int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag;
int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
if (firstSupported > (int)SystemState.TitleLanguage.BrazilianPortuguese)
@ -190,7 +190,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// GetDisplayVersion() -> nn::oe::DisplayVersion
public ResultCode GetDisplayVersion(ServiceCtx context)
{
// This should work as DisplayVersion U8Span always gives a 0x10 size byte array.
// If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion);
@ -252,7 +251,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
out CacheStorageTargetMedia storageTarget, applicationId, ref controlHolder.Value, index, saveSize,
out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize,
journalSize);
if (result.IsFailure()) return (ResultCode)result.Value;

View File

@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
{
launchProperty = new LibHac.Arp.ApplicationLaunchProperty
{
BaseStorageId = StorageId.BuiltInUser,
StorageId = StorageId.BuiltInUser,
ApplicationId = ApplicationId
};
@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
{
launchProperty = new LibHac.Arp.ApplicationLaunchProperty
{
BaseStorageId = StorageId.BuiltInUser,
StorageId = StorageId.BuiltInUser,
ApplicationId = applicationId
};

View File

@ -132,22 +132,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
}
}
public static Result ReadFsPath(out FsPath path, ServiceCtx context, int index = 0)
{
ulong position = context.Request.PtrBuff[index].Position;
ulong size = context.Request.PtrBuff[index].Size;
byte[] pathBytes = new byte[size];
context.Memory.Read(position, pathBytes);
return FsPath.FromSpan(out path, pathBytes);
}
public static ref readonly FspPath GetFspPath(ServiceCtx context, int index = 0)
{
ulong position = (ulong)context.Request.PtrBuff[index].Position;
ulong size = (ulong)context.Request.PtrBuff[index].Size;
ulong position = context.Request.PtrBuff[index].Position;
ulong size = context.Request.PtrBuff[index].Size;
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
ReadOnlySpan<FspPath> fspBuffer = MemoryMarshal.Cast<byte, FspPath>(buffer);
@ -157,8 +145,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
public static ref readonly LibHac.FsSrv.Sf.Path GetSfPath(ServiceCtx context, int index = 0)
{
ulong position = (ulong)context.Request.PtrBuff[index].Position;
ulong size = (ulong)context.Request.PtrBuff[index].Size;
ulong position = context.Request.PtrBuff[index].Position;
ulong size = context.Request.PtrBuff[index].Size;
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
ReadOnlySpan<LibHac.FsSrv.Sf.Path> pathBuffer = MemoryMarshal.Cast<byte, LibHac.FsSrv.Sf.Path>(buffer);

View File

@ -1,6 +1,6 @@
using LibHac;
using LibHac.Common;
using LibHac.FsSrv;
using LibHac.Fs;
namespace Ryujinx.HLE.HOS.Services.Fs
{

View File

@ -1,13 +1,15 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Olsc
{
[Service("olsc:u")] // 10.0.0+
class IOlscServiceForApplication : IpcService
{
private bool _initialized;
private bool _initialized;
private Dictionary<UserId, bool> _saveDataBackupSettingDatabase;
public IOlscServiceForApplication(ServiceCtx context) { }
@ -16,7 +18,9 @@ namespace Ryujinx.HLE.HOS.Services.Olsc
public ResultCode Initialize(ServiceCtx context)
{
// NOTE: Service call arp:r GetApplicationInstanceUnregistrationNotifier with the pid and initialize some internal struct.
// Since we will not support online savedata backup. It's fine to stub it for now.
// Since we will not support online savedata backup, it's fine to stub it for now.
_saveDataBackupSettingDatabase = new Dictionary<UserId, bool>();
_initialized = true;
@ -25,12 +29,11 @@ namespace Ryujinx.HLE.HOS.Services.Olsc
return ResultCode.Success;
}
[CommandHipc(14)]
// SetSaveDataBackupSettingEnabled(nn::account::Uid, bool)
public ResultCode SetSaveDataBackupSettingEnabled(ServiceCtx context)
[CommandHipc(13)]
// GetSaveDataBackupSetting(nn::account::Uid) -> u8
public ResultCode GetSaveDataBackupSetting(ServiceCtx context)
{
UserId userId = context.RequestData.ReadStruct<UserId>();
ulong saveDataBackupSettingEnabled = context.RequestData.ReadUInt64();
UserId userId = context.RequestData.ReadStruct<UserId>();
if (!_initialized)
{
@ -42,8 +45,42 @@ namespace Ryujinx.HLE.HOS.Services.Olsc
return ResultCode.NullArgument;
}
// NOTE: Service store the UserId and the boolean in an internal SaveDataBackupSettingDatabase object.
// Since we will not support online savedata backup. It's fine to stub it for now.
if (_saveDataBackupSettingDatabase[userId])
{
context.ResponseData.Write((byte)1); // TODO: Determine value.
}
else
{
context.ResponseData.Write((byte)2); // TODO: Determine value.
}
// NOTE: Since we will not support online savedata backup, it's fine to stub it for now.
Logger.Stub?.PrintStub(LogClass.ServiceOlsc, new { userId });
return ResultCode.Success;
}
[CommandHipc(14)]
// SetSaveDataBackupSettingEnabled(nn::account::Uid, bool)
public ResultCode SetSaveDataBackupSettingEnabled(ServiceCtx context)
{
bool saveDataBackupSettingEnabled = context.RequestData.ReadUInt64() != 0;
UserId userId = context.RequestData.ReadStruct<UserId>();
if (!_initialized)
{
return ResultCode.NotInitialized;
}
if (userId.IsNull)
{
return ResultCode.NullArgument;
}
_saveDataBackupSettingDatabase[userId] = saveDataBackupSettingEnabled;
// NOTE: Since we will not support online savedata backup, it's fine to stub it for now.
Logger.Stub?.PrintStub(LogClass.ServiceOlsc, new { userId, saveDataBackupSettingEnabled });

View File

@ -1,8 +1,9 @@
using LibHac.Ns;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Arp;
using System;
using static LibHac.Ns.ApplicationControlProperty;
namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
{
class IParentalControlService : IpcService
@ -52,8 +53,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
_titleId = titleId;
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
_ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
_parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControl;
_ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
_parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag;
}
}
@ -224,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
private ResultCode IsStereoVisionPermittedImpl()
{
/*
// TODO: Application Exemptions are readed from file "appExemptions.dat" in the service savedata.
// TODO: Application Exemptions are read from file "appExemptions.dat" in the service savedata.
// Since we don't support the pctl savedata for now, this can be implemented later.
if (appExemption)

View File

@ -15,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
{
ref readonly var controlProperty = ref context.Device.Application.ControlData.Value;
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
@ -31,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
}
}
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Application.ControlData.Value.PlayLogQueryCapability;
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)controlProperty.PlayLogQueryCapability;
List<ulong> titleIds = new List<ulong>();
@ -45,7 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
// Check if input title ids are in the whitelist.
foreach (ulong titleId in titleIds)
{
if (!context.Device.Application.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
if (!controlProperty.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId))
{
return (ResultCode)Am.ResultCode.ObjectInvalid;
}

View File

@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
if (serviceType != ViServiceType.Application)
{
return ResultCode.InvalidRange;
return ResultCode.PermissionDenied;
}
MakeObject(context, new IApplicationDisplayService(serviceType));

View File

@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
if (serviceType != ViServiceType.Manager)
{
return ResultCode.InvalidRange;
return ResultCode.PermissionDenied;
}
MakeObject(context, new IApplicationDisplayService(serviceType));

View File

@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
if (serviceType != ViServiceType.System)
{
return ResultCode.InvalidRange;
return ResultCode.PermissionDenied;
}
MakeObject(context, new IApplicationDisplayService(serviceType));

View File

@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi
InvalidArguments = (1 << ErrorCodeShift) | ModuleId,
InvalidLayerSize = (4 << ErrorCodeShift) | ModuleId,
InvalidRange = (5 << ErrorCodeShift) | ModuleId,
PermissionDenied = (5 << ErrorCodeShift) | ModuleId,
InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId,
InvalidValue = (7 << ErrorCodeShift) | ModuleId,
AlreadyOpened = (9 << ErrorCodeShift) | ModuleId

View File

@ -1,7 +1,6 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
@ -22,16 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
{
private readonly ViServiceType _serviceType;
private readonly List<DisplayInfo> _displayInfo;
private readonly Dictionary<ulong, DisplayInfo> _openDisplayInfo;
private class DisplayState
{
public int RetrievedEventsCount;
}
private readonly List<DisplayInfo> _displayInfo;
private readonly Dictionary<ulong, DisplayState> _openDisplays;
private int _vsyncEventHandle;
public IApplicationDisplayService(ViServiceType serviceType)
{
_serviceType = serviceType;
_displayInfo = new List<DisplayInfo>();
_openDisplayInfo = new Dictionary<ulong, DisplayInfo>();
_serviceType = serviceType;
_displayInfo = new List<DisplayInfo>();
_openDisplays = new Dictionary<ulong, DisplayState>();
void AddDisplayInfo(string name, bool layerLimitEnabled, ulong layerLimitMax, ulong width, ulong height)
{
@ -64,7 +68,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
// FIXME: Should be _serviceType != ViServiceType.Application but guests crashes if we do this check.
if (_serviceType > ViServiceType.System)
{
return ResultCode.InvalidRange;
return ResultCode.PermissionDenied;
}
MakeObject(context, new HOSBinderDriverServer());
@ -79,7 +83,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
// FIXME: Should be _serviceType == ViServiceType.System but guests crashes if we do this check.
if (_serviceType > ViServiceType.System)
{
return ResultCode.InvalidRange;
return ResultCode.PermissionDenied;
}
MakeObject(context, new ISystemDisplayService(this));
@ -93,7 +97,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
{
if (_serviceType > ViServiceType.System)
{
return ResultCode.InvalidRange;
return ResultCode.PermissionDenied;
}
MakeObject(context, new IManagerDisplayService(this));
@ -107,7 +111,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
{
if (_serviceType > ViServiceType.System)
{
return ResultCode.InvalidRange;
return ResultCode.PermissionDenied;
}
MakeObject(context, new HOSBinderDriverServer());
@ -174,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
return ResultCode.InvalidValue;
}
if (!_openDisplayInfo.TryAdd((ulong)displayId, _displayInfo[displayId]))
if (!_openDisplays.TryAdd((ulong)displayId, new DisplayState()))
{
return ResultCode.AlreadyOpened;
}
@ -190,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
{
ulong displayId = context.RequestData.ReadUInt64();
if (!_openDisplayInfo.Remove(displayId))
if (!_openDisplays.Remove(displayId))
{
return ResultCode.InvalidValue;
}
@ -454,11 +458,16 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
{
ulong displayId = context.RequestData.ReadUInt64();
if (!_openDisplayInfo.ContainsKey(displayId))
if (!_openDisplays.TryGetValue(displayId, out DisplayState displayState))
{
return ResultCode.InvalidValue;
}
if (displayState.RetrievedEventsCount > 0)
{
return ResultCode.PermissionDenied;
}
if (_vsyncEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(context.Device.System.VsyncEvent.ReadableEvent, out _vsyncEventHandle) != KernelResult.Success)
@ -467,6 +476,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
}
}
displayState.RetrievedEventsCount++;
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_vsyncEventHandle);
return ResultCode.Success;

View File

@ -1,4 +1,4 @@
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Fs;
using LibHac.Loader;
using LibHac.Tools.FsSystem;
@ -26,8 +26,8 @@ namespace Ryujinx.HLE.Loaders.Executables
public uint DataSize { get; }
public uint BssSize { get; }
public string Name;
public Buffer32 BuildId;
public string Name;
public Array32<byte> BuildId;
public NsoExecutable(IStorage inStorage, string name = null)
{

View File

@ -19,7 +19,7 @@
<ItemGroup>
<PackageReference Include="Concentus" Version="1.1.7" />
<PackageReference Include="LibHac" Version="0.15.0" />
<PackageReference Include="LibHac" Version="0.16.0" />
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />

View File

@ -220,6 +220,7 @@ namespace Ryujinx.Headless.SDL2
StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
@ -241,6 +242,7 @@ namespace Ryujinx.Headless.SDL2
StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
Motion = new StandardMotionConfigController
@ -310,7 +312,7 @@ namespace Ryujinx.Headless.SDL2
{
controllerConfig.RangeLeft = 1.0f;
controllerConfig.RangeRight = 1.0f;
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
}
}
@ -396,7 +398,7 @@ namespace Ryujinx.Headless.SDL2
if ((bool)option.EnableFileLog)
{
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(AppDomain.CurrentDomain.BaseDirectory, "file"),
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
1000,
AsyncLogTargetOverflowAction.Block
));

View File

@ -350,6 +350,14 @@ namespace Ryujinx.Input.SDL2
{
resultY = -resultY;
}
if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.Rotate90CW) ||
(inputId == StickInputId.Right && _configuration.RightJoyconStick.Rotate90CW))
{
float temp = resultX;
resultX = resultY;
resultY = -temp;
}
}
return (resultX, resultY);

View File

@ -1,4 +1,5 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -81,7 +82,7 @@ namespace Ryujinx.SDL2.Common
SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE);
string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt");
string gamepadDbPath = Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt");
if (File.Exists(gamepadDbPath))
{

View File

@ -257,6 +257,35 @@ namespace Ryujinx.Tests.Cpu
return GetContext();
}
public void RunPrecomputedTestCase(PrecomputedThumbTestCase test)
{
foreach (ushort instruction in test.Instructions)
{
ThumbOpcode(instruction);
}
for (int i = 0; i < 15; i++)
{
GetContext().SetX(i, test.StartRegs[i]);
}
uint startCpsr = test.StartRegs[15];
for (int i = 0; i < 32; i++)
{
GetContext().SetPstateFlag((PState)i, (startCpsr & (1u << i)) != 0);
}
ExecuteOpcodes(runUnicorn: false);
for (int i = 0; i < 15; i++)
{
Assert.That(GetContext().GetX(i), Is.EqualTo(test.FinalRegs[i]));
}
uint finalCpsr = test.FinalRegs[15];
Assert.That(GetContext().Pstate, Is.EqualTo(finalCpsr));
}
protected void SetWorkingMemory(uint offset, byte[] data)
{
_memory.Write(DataBaseAddress + offset, data);

View File

@ -0,0 +1,57 @@
#define AluRs32
using NUnit.Framework;
using System.Runtime.CompilerServices;
namespace Ryujinx.Tests.Cpu
{
[Category("AluImm32")]
public sealed class CpuTestAluImm32 : CpuTest32
{
#if AluRs32
#region "ValueSource (Opcodes)"
private static uint[] _opcodes()
{
return new uint[]
{
0xe2a00000u, // ADC R0, R0, #0
0xe2b00000u, // ADCS R0, R0, #0
0xe2800000u, // ADD R0, R0, #0
0xe2900000u, // ADDS R0, R0, #0
0xe3c00000u, // BIC R0, R0, #0
0xe3d00000u, // BICS R0, R0, #0
0xe2600000u, // RSB R0, R0, #0
0xe2700000u, // RSBS R0, R0, #0
0xe2e00000u, // RSC R0, R0, #0
0xe2f00000u, // RSCS R0, R0, #0
0xe2c00000u, // SBC R0, R0, #0
0xe2d00000u, // SBCS R0, R0, #0
0xe2400000u, // SUB R0, R0, #0
0xe2500000u, // SUBS R0, R0, #0
};
}
#endregion
private const int RndCnt = 2;
private const int RndCntAmount = 2;
[Test, Pairwise]
public void TestCpuTestAluImm32([ValueSource("_opcodes")] uint opcode,
[Values(0u, 13u)] uint rd,
[Values(1u, 13u)] uint rn,
[Random(RndCnt)] uint imm,
[Random(RndCnt)] uint wn,
[Values(true, false)] bool carryIn)
{
opcode |= ((imm & 0xfff) << 0) | ((rn & 15) << 16) | ((rd & 15) << 12);
uint sp = TestContext.CurrentContext.Random.NextUInt();
SingleOpcode(opcode, r1: wn, sp: sp, carry: carryIn);
CompareAgainstUnicorn();
}
#endif
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
using ARMeilleure.State;
using NUnit.Framework;
namespace Ryujinx.Tests.Cpu
{
[Category("T32Flow")]
public sealed class CpuTestT32Flow : CpuTest32
{
[Test]
public void TestT32B1()
{
// BNE label
ThumbOpcode(0xf040);
ThumbOpcode(0x8240);
for (int i = 0; i < 576; i++)
{
ThumbOpcode(0xe7fe);
}
// label: BX LR
ThumbOpcode(0x4770);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
}
[Test]
public void TestT32B2()
{
// BNE label1
ThumbOpcode(0xf040);
ThumbOpcode(0x8242);
// label2: BNE label3
ThumbOpcode(0xf040);
ThumbOpcode(0x8242);
for (int i = 0; i < 576; i++)
{
ThumbOpcode(0xe7fe);
}
// label1: BNE label2
ThumbOpcode(0xf47f);
ThumbOpcode(0xadbc);
// label3: BX LR
ThumbOpcode(0x4770);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
}
[Test]
public void TestT32B3()
{
// B.W label
ThumbOpcode(0xf000);
ThumbOpcode(0xba40);
for (int i = 0; i < 576; i++)
{
ThumbOpcode(0xe7fe);
}
// label: BX LR
ThumbOpcode(0x4770);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
}
[Test]
public void TestT32B4()
{
// B.W label1
ThumbOpcode(0xf000);
ThumbOpcode(0xba42);
// label2: B.W label3
ThumbOpcode(0xf000);
ThumbOpcode(0xba42);
for (int i = 0; i < 576; i++)
{
ThumbOpcode(0xe7fe);
}
// label1: B.W label2
ThumbOpcode(0xf7ff);
ThumbOpcode(0xbdbc);
// label3: BX LR
ThumbOpcode(0x4770);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
}
[Test]
public void TestT32Bl()
{
// BL label
ThumbOpcode(0xf000);
ThumbOpcode(0xf840);
for (int i = 0; i < 64; i++)
{
ThumbOpcode(0xe7fe);
}
ThumbOpcode(0x4670); // label: MOV R0, LR
ThumbOpcode(0x2100); // MOVS R1, #0
ThumbOpcode(0x468e); // MOV LR, R1
ThumbOpcode(0x4770); // BX LR
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005));
}
[Test]
public void TestT32Blx1()
{
// BLX label
ThumbOpcode(0xf000);
ThumbOpcode(0xe840);
for (int i = 0; i < 64; i++)
{
ThumbOpcode(0x4770);
}
// .arm ; label: MOV R0, LR
Opcode(0xe1a0000e);
// MOV LR, #0
Opcode(0xe3a0e000);
// BX LR
Opcode(0xe12fff1e);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005));
Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false));
}
[Test]
public void TestT32Blx2()
{
// NOP
ThumbOpcode(0xbf00);
// BLX label
ThumbOpcode(0xf000);
ThumbOpcode(0xe840);
for (int i = 0; i < 63; i++)
{
ThumbOpcode(0x4770);
}
// .arm ; label: MOV R0, LR
Opcode(0xe1a0000e);
// MOV LR, #0
Opcode(0xe3a0e000);
// BX LR
Opcode(0xe12fff1e);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1007));
Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false));
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Tests.Cpu
{
public class PrecomputedThumbTestCase
{
public ushort[] Instructions;
public uint[] StartRegs;
public uint[] FinalRegs;
}
}

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 36;
public const int CurrentVersion = 37;
/// <summary>
/// Version of the configuration file format
@ -236,6 +236,11 @@ namespace Ryujinx.Configuration
/// </summary>
public bool StartFullscreen { get; set; }
/// <summary>
/// Show console window
/// </summary>
public bool ShowConsole { get; set; }
/// <summary>
/// Enable or disable keyboard support (Independent from controllers binding)
/// </summary>

View File

@ -6,6 +6,7 @@ using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Configuration.System;
using Ryujinx.Configuration.Ui;
using Ryujinx.Ui.Helper;
using System;
using System.Collections.Generic;
@ -88,6 +89,11 @@ namespace Ryujinx.Configuration
/// </summary>
public ReactiveObject<bool> StartFullscreen { get; private set; }
/// <summary>
/// Hide / Show Console Window
/// </summary>
public ReactiveObject<bool> ShowConsole { get; private set; }
public UiSection()
{
GuiColumns = new Columns();
@ -96,6 +102,8 @@ namespace Ryujinx.Configuration
EnableCustomTheme = new ReactiveObject<bool>();
CustomThemePath = new ReactiveObject<string>();
StartFullscreen = new ReactiveObject<bool>();
ShowConsole = new ReactiveObject<bool>();
ShowConsole.Event += static (s, e) => { ConsoleHelper.SetConsoleWindowState(e.NewValue); };
}
}
@ -508,6 +516,7 @@ namespace Ryujinx.Configuration
EnableCustomTheme = Ui.EnableCustomTheme,
CustomThemePath = Ui.CustomThemePath,
StartFullscreen = Ui.StartFullscreen,
ShowConsole = Ui.ShowConsole,
EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse,
Hotkeys = Hid.Hotkeys,
@ -574,6 +583,7 @@ namespace Ryujinx.Configuration
Ui.EnableCustomTheme.Value = false;
Ui.CustomThemePath.Value = "";
Ui.StartFullscreen.Value = false;
Ui.ShowConsole.Value = true;
Hid.EnableKeyboard.Value = false;
Hid.EnableMouse.Value = false;
Hid.Hotkeys.Value = new KeyboardHotkeys
@ -995,7 +1005,7 @@ namespace Ryujinx.Configuration
controllerConfig.RangeRight = 1.0f;
}
}
configurationFileUpdated = true;
}
@ -1007,7 +1017,16 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true;
}
if (configurationFileFormat.Version < 37)
{
Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 37.");
configurationFileFormat.ShowConsole = true;
configurationFileUpdated = true;
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
@ -1061,6 +1080,7 @@ namespace Ryujinx.Configuration
Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme;
Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath;
Ui.StartFullscreen.Value = configurationFileFormat.StartFullscreen;
Ui.ShowConsole.Value = configurationFileFormat.ShowConsole;
Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard;
Hid.EnableMouse.Value = configurationFileFormat.EnableMouse;
Hid.Hotkeys.Value = configurationFileFormat.Hotkeys;

View File

@ -80,7 +80,7 @@ namespace Ryujinx.Configuration
if (e.NewValue)
{
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(AppDomain.CurrentDomain.BaseDirectory, "file"),
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
1000,
AsyncLogTargetOverflowAction.Block
));

View File

@ -311,7 +311,7 @@ namespace Ryujinx.Modules
catch (Exception e)
{
Logger.Warning?.Print(LogClass.Application, e.Message);
Logger.Warning?.Print(LogClass.Application, $"Multi-Threaded update failed, falling back to single-threaded updater.");
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
@ -327,8 +327,8 @@ namespace Ryujinx.Modules
catch (WebException ex)
{
Logger.Warning?.Print(LogClass.Application, ex.Message);
Logger.Warning?.Print(LogClass.Application, $"Multi-Threaded update failed, falling back to single-threaded updater.");
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
for (int j = 0; j < webClients.Count; j++)
{
webClients[j].CancelAsync();
@ -567,7 +567,14 @@ namespace Ryujinx.Modules
#else
if (showWarnings)
{
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
if (ReleaseInformations.IsFlatHubBuild())
{
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub.");
}
else
{
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
}
}
return false;

View File

@ -27,6 +27,8 @@ namespace Ryujinx
public static string ConfigurationPath { get; set; }
public static string CommandLineProfile { get; set; }
[DllImport("libX11")]
private extern static int XInitThreads();
@ -52,6 +54,17 @@ namespace Ryujinx
baseDirPathArg = args[++i];
}
else if (arg == "-p" || arg == "--profile")
{
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
CommandLineProfile = args[++i];
}
else if (arg == "-f" || arg == "--fullscreen")
{
startFullscreenArg = true;

View File

@ -558,10 +558,10 @@ namespace Ryujinx.Ui.App
{
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
if (controlData.Titles.Length > (int)desiredTitleLanguage)
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
{
titleName = controlData.Titles[(int)desiredTitleLanguage].Name.ToString();
publisher = controlData.Titles[(int)desiredTitleLanguage].Publisher.ToString();
titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
}
else
{
@ -571,11 +571,11 @@ namespace Ryujinx.Ui.App
if (string.IsNullOrWhiteSpace(titleName))
{
foreach (ApplicationControlTitle controlTitle in controlData.Titles)
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!((U8Span)controlTitle.Name).IsEmpty())
if (!controlTitle.NameString.IsEmpty())
{
titleName = controlTitle.Name.ToString();
titleName = controlTitle.NameString.ToString();
break;
}
@ -584,11 +584,11 @@ namespace Ryujinx.Ui.App
if (string.IsNullOrWhiteSpace(publisher))
{
foreach (ApplicationControlTitle controlTitle in controlData.Titles)
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!((U8Span)controlTitle.Publisher).IsEmpty())
if (!controlTitle.PublisherString.IsEmpty())
{
publisher = controlTitle.Publisher.ToString();
publisher = controlTitle.PublisherString.ToString();
break;
}
@ -599,7 +599,7 @@ namespace Ryujinx.Ui.App
{
titleId = controlData.PresenceGroupId.ToString("x16");
}
else if (controlData.SaveDataOwnerId.Value != 0)
else if (controlData.SaveDataOwnerId != 0)
{
titleId = controlData.SaveDataOwnerId.ToString();
}
@ -612,7 +612,7 @@ namespace Ryujinx.Ui.App
titleId = "0000000000000000";
}
version = controlData.DisplayVersion.ToString();
version = controlData.DisplayVersionString.ToString();
}
private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)

View File

@ -0,0 +1,49 @@
using Ryujinx.Common.Logging;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Ui.Helper
{
public static class ConsoleHelper
{
public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
public static void SetConsoleWindowState(bool show)
{
if (OperatingSystem.IsWindows())
{
SetConsoleWindowStateWindows(show);
}
else if (show == false)
{
Logger.Warning?.Print(LogClass.Application, "OS doesn't support hiding console window");
}
}
[SupportedOSPlatform("windows")]
private static void SetConsoleWindowStateWindows(bool show)
{
const int SW_HIDE = 0;
const int SW_SHOW = 5;
IntPtr hWnd = GetConsoleWindow();
if (hWnd == IntPtr.Zero)
{
Logger.Warning?.Print(LogClass.Application, "Attempted to show/hide console window but console window does not exist");
return;
}
ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);
}
[SupportedOSPlatform("windows")]
[DllImport("kernel32")]
static extern IntPtr GetConsoleWindow();
[SupportedOSPlatform("windows")]
[DllImport("user32")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}
}

View File

@ -107,6 +107,7 @@ namespace Ryujinx.Ui
[GUI] MenuItem _hideUi;
[GUI] MenuItem _fullScreen;
[GUI] CheckMenuItem _startFullScreen;
[GUI] CheckMenuItem _showConsole;
[GUI] CheckMenuItem _favToggle;
[GUI] MenuItem _firmwareInstallDirectory;
[GUI] MenuItem _firmwareInstallFile;
@ -178,7 +179,7 @@ namespace Ryujinx.Ui
VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient);
_contentManager = new ContentManager(_virtualFileSystem);
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, Program.CommandLineProfile);
_userChannelPersistence = new UserChannelPersistence();
// Instantiate GUI objects.
@ -213,6 +214,9 @@ namespace Ryujinx.Ui
_startFullScreen.Active = true;
}
_showConsole.Active = ConfigurationState.Instance.Ui.ShowConsole.Value;
_showConsole.Visible = ConsoleHelper.SetConsoleWindowStateSupported;
_actionMenu.Sensitive = false;
_pauseEmulation.Sensitive = false;
_resumeEmulation.Sensitive = false;
@ -1291,7 +1295,7 @@ namespace Ryujinx.Ui
private void OpenLogsFolder_Pressed(object sender, EventArgs args)
{
string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
string logPath = System.IO.Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "Logs");
new DirectoryInfo(logPath).Create();
@ -1535,6 +1539,13 @@ namespace Ryujinx.Ui
SaveConfig();
}
private void ShowConsole_Toggled(object sender, EventArgs args)
{
ConfigurationState.Instance.Ui.ShowConsole.Value = _showConsole.Active;
SaveConfig();
}
private void OptionMenu_StateChanged(object o, StateChangedArgs args)
{
_manageUserProfiles.Sensitive = _emulationContext == null;

View File

@ -142,6 +142,15 @@
<signal name="toggled" handler="StartFullScreen_Toggled" swapped="no"/>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="_showConsole">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Show Log Console</property>
<property name="use_underline">True</property>
<signal name="toggled" handler="ShowConsole_Toggled" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>

View File

@ -11,6 +11,7 @@ namespace Ryujinx.Ui.Widgets
private MenuItem _manageDlcMenuItem;
private MenuItem _manageCheatMenuItem;
private MenuItem _openTitleModDirMenuItem;
private MenuItem _openTitleSdModDirMenuItem;
private Menu _extractSubMenu;
private MenuItem _extractMenuItem;
private MenuItem _extractRomFsMenuItem;
@ -88,6 +89,15 @@ namespace Ryujinx.Ui.Widgets
};
_openTitleModDirMenuItem.Activated += OpenTitleModDir_Clicked;
//
// _openTitleSdModDirMenuItem
//
_openTitleSdModDirMenuItem = new MenuItem("Open Atmosphere Mods Directory")
{
TooltipText = "Open the alternative SD card atmosphere directory which contains the Application's Mods."
};
_openTitleSdModDirMenuItem.Activated += OpenTitleSdModDir_Clicked;
//
// _extractSubMenu
//
@ -199,6 +209,7 @@ namespace Ryujinx.Ui.Widgets
Add(_manageDlcMenuItem);
Add(_manageCheatMenuItem);
Add(_openTitleModDirMenuItem);
Add(_openTitleSdModDirMenuItem);
Add(new SeparatorMenuItem());
Add(_manageCacheMenuItem);
Add(_extractMenuItem);

View File

@ -6,7 +6,6 @@ using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Ns;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
@ -26,8 +25,6 @@ using System.IO;
using System.Reflection;
using System.Threading;
using static LibHac.Fs.ApplicationSaveDataManagement;
namespace Ryujinx.Ui.Widgets
{
public partial class GameTableContextMenu : Menu
@ -81,7 +78,7 @@ namespace Ryujinx.Ui.Widgets
PopupAtPointer(null);
}
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId)
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
{
saveDataId = default;
@ -121,7 +118,7 @@ namespace Ryujinx.Ui.Widgets
Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
result = EnsureApplicationSaveData(_horizonClient.Fs, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user);
if (result.IsFailure())
{
@ -146,11 +143,9 @@ namespace Ryujinx.Ui.Widgets
return false;
}
private void OpenSaveDir(SaveDataFilter saveDataFilter)
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
{
saveDataFilter.SetProgramId(new ProgramId(_titleId));
if (!TryFindSaveData(_titleName, _titleId, _controlData, saveDataFilter, out ulong saveDataId))
if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId))
{
return;
}
@ -439,26 +434,24 @@ namespace Ryujinx.Ui.Widgets
//
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
{
SaveDataFilter saveDataFilter = new SaveDataFilter();
saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low));
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDir(saveDataFilter);
OpenSaveDir(in saveDataFilter);
}
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
{
SaveDataFilter saveDataFilter = new SaveDataFilter();
saveDataFilter.SetSaveDataType(SaveDataType.Device);
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default);
OpenSaveDir(saveDataFilter);
OpenSaveDir(in saveDataFilter);
}
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
{
SaveDataFilter saveDataFilter = new SaveDataFilter();
saveDataFilter.SetSaveDataType(SaveDataType.Bcat);
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
OpenSaveDir(saveDataFilter);
OpenSaveDir(in saveDataFilter);
}
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
@ -484,6 +477,14 @@ namespace Ryujinx.Ui.Widgets
OpenHelper.OpenFolder(titleModsPath);
}
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
{
string sdModsBasePath = _virtualFileSystem.ModLoader.GetSdModsBasePath();
string titleModsPath = _virtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, _titleIdText);
OpenHelper.OpenFolder(titleModsPath);
}
private void ExtractRomFs_Clicked(object sender, EventArgs args)
{
ExtractSection(NcaSectionType.Data);

View File

@ -73,6 +73,7 @@ namespace Ryujinx.Ui.Windows
[GUI] ToggleButton _lStick;
[GUI] CheckButton _invertLStickX;
[GUI] CheckButton _invertLStickY;
[GUI] CheckButton _rotateL90CW;
[GUI] ToggleButton _lStickUp;
[GUI] ToggleButton _lStickDown;
[GUI] ToggleButton _lStickLeft;
@ -88,6 +89,7 @@ namespace Ryujinx.Ui.Windows
[GUI] ToggleButton _rStick;
[GUI] CheckButton _invertRStickX;
[GUI] CheckButton _invertRStickY;
[GUI] CheckButton _rotateR90CW;
[GUI] ToggleButton _rStickUp;
[GUI] ToggleButton _rStickDown;
[GUI] ToggleButton _rStickLeft;
@ -490,6 +492,7 @@ namespace Ryujinx.Ui.Windows
_lStick.Label = controllerConfig.LeftJoyconStick.Joystick.ToString();
_invertLStickX.Active = controllerConfig.LeftJoyconStick.InvertStickX;
_invertLStickY.Active = controllerConfig.LeftJoyconStick.InvertStickY;
_rotateL90CW.Active = controllerConfig.LeftJoyconStick.Rotate90CW;
_lStickButton.Label = controllerConfig.LeftJoyconStick.StickButton.ToString();
_dpadUp.Label = controllerConfig.LeftJoycon.DpadUp.ToString();
_dpadDown.Label = controllerConfig.LeftJoycon.DpadDown.ToString();
@ -503,6 +506,7 @@ namespace Ryujinx.Ui.Windows
_rStick.Label = controllerConfig.RightJoyconStick.Joystick.ToString();
_invertRStickX.Active = controllerConfig.RightJoyconStick.InvertStickX;
_invertRStickY.Active = controllerConfig.RightJoyconStick.InvertStickY;
_rotateR90CW.Active = controllerConfig.RightJoyconStick.Rotate90CW;
_rStickButton.Label = controllerConfig.RightJoyconStick.StickButton.ToString();
_a.Label = controllerConfig.RightJoycon.ButtonA.ToString();
_b.Label = controllerConfig.RightJoycon.ButtonB.ToString();
@ -718,6 +722,7 @@ namespace Ryujinx.Ui.Windows
Joystick = lStick,
InvertStickY = _invertLStickY.Active,
StickButton = lStickButton,
Rotate90CW = _rotateL90CW.Active,
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
@ -737,6 +742,7 @@ namespace Ryujinx.Ui.Windows
Joystick = rStick,
InvertStickY = _invertRStickY.Active,
StickButton = rStickButton,
Rotate90CW = _rotateR90CW.Active,
},
Motion = motionConfig,
Rumble = new RumbleConfigController
@ -1056,6 +1062,7 @@ namespace Ryujinx.Ui.Windows
StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
@ -1077,6 +1084,7 @@ namespace Ryujinx.Ui.Windows
StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
Motion = new StandardMotionConfigController

View File

@ -740,6 +740,19 @@
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="_rotateL90CW">
<property name="label" translatable="yes">Rotate 90° Clockwise</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -1697,6 +1710,19 @@
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="_rotateR90CW">
<property name="label" translatable="yes">Rotate 90° Clockwise</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@ -105,7 +105,7 @@ namespace Ryujinx.Ui.Windows
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersion.ToString()} - {path}");
RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersionString.ToString()} - {path}");
radioButton.JoinGroup(_noUpdateRadioButton);
_availableUpdatesBox.Add(radioButton);

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 255.76 255.76"><defs><style>.cls-1{fill:#02c5e5;}.cls-2{fill:#ff5f55;}.cls-3{fill:none;}</style></defs><g id="Ebene_2" data-name="Ebene 2"><g id="Ebene_1-2" data-name="Ebene 1"><g id="Ebene_2-2" data-name="Ebene 2"><g id="Ebene_1-2-2" data-name="Ebene 1-2"><path class="cls-1" d="M80.63,0V220.39H44.37c-14,0-35.74-20.74-35.74-39.13V40.13C8.63,19.19,31.36,0,49.06,0Z"/><path class="cls-2" d="M175.13,35.37V255.76h36.26c14,0,35.74-20.74,35.74-39.13V75.5c0-20.94-22.73-40.13-40.43-40.13Z"/><polygon class="cls-1" points="124.34 137.96 122.58 145.57 90.64 145.57 92.89 137.96 124.34 137.96"/><polygon class="cls-2" points="160.29 137.96 157.84 145.57 122.58 145.57 124.34 137.96 160.29 137.96"/><polygon class="cls-1" points="130.39 111.86 128.62 119.47 95.14 119.47 97.39 111.86 130.39 111.86"/><polygon class="cls-2" points="164.79 111.86 162.34 119.47 128.62 119.47 130.39 111.86 164.79 111.86"/><polygon class="cls-1" points="104.24 167.99 122.83 87.77 129.78 87.77 111.19 167.99 104.24 167.99"/><polygon class="cls-2" points="128.18 167.99 146.77 87.77 153.89 87.77 135.3 167.99 128.18 167.99"/></g><rect class="cls-3" width="255.76" height="255.76"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-nx-nca">
<comment>Nintendo Content Archive</comment>
<glob pattern="*.nca"/>
</mime-type>
<mime-type type="application/x-nx-nro">
<comment>Nintendo Relocatable Object</comment>
<glob pattern="*.nro"/>
</mime-type>
<mime-type type="application/x-nx-nso">
<comment>Nintendo Shared Object</comment>
<glob pattern="*.nso"/>
</mime-type>
<mime-type type="application/x-nx-nsp">
<comment>Nintendo Submission Package</comment>
<glob pattern="*.nsp"/>
</mime-type>
<mime-type type="application/x-nx-xci">
<comment>Nintendo Switch Cartridge</comment>
<glob pattern="*.xci"/>
</mime-type>
</mime-info>

View File

@ -0,0 +1,14 @@
[Desktop Entry]
Version=1.0
Name=Ryujinx
Comment=A Nintendo Switch Emulator
Type=Application
GenericName=Nintendo Switch Emulator
Icon=ryujinx
Terminal=false
Exec=Ryujinx %f
Categories=Game;Emulator;GTK;
MimeType=application/x-nx-nca;application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci;
Keywords=Switch;Nintendo;Emulator;
StartupWMClass=Ryujinx
PrefersNonDefaultGPU=true