Compare commits

...

22 Commits

Author SHA1 Message Date
gdkchan
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
merry
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
gdkchan
8c9633d72f Initialize indexed inputs used on next shader stage (#3198) 2022-03-14 20:02:50 -03:00
gdkchan
1f93fd52d9 Do not initialize geometry shader passthrough attributes (#3196) 2022-03-14 19:35:41 -03:00
Ac_K
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
darko1979
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
gdkchan
69b05f9918 Fix GetUserDisableCount NRE (#3187) 2022-03-12 18:12:12 +01:00
gdkchan
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
merry
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
Mary
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
merry
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
MutantAura
ee174be57c Mod loading from atmosphere SD directories (#3176)
* initial sd support

* GUI option

* alignment

* review changes
2022-03-06 22:12:01 +01:00
gdkchan
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
merry
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
merry
747081d2c7 Decoders: Fix instruction lengths for 16-bit B instructions (#3177) 2022-03-05 16:20:24 +01:00
merry
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
merry
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
Mary
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
JavidPack
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
Alex Barney
3705c20668 Update LibHac to v0.16.0 (#3159) 2022-02-27 00:52:25 +01:00
merry
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
gdkchan
0a24aa6af2 Allow textures to have their data partially mapped (#2629)
* Allow textures to have their data partially mapped

* Explicitly check for invalid memory ranges on the MultiRangeList

* Update GetWritableRegion to also support unmapped ranges
2022-02-22 13:34:16 -03:00
85 changed files with 2469 additions and 392 deletions

View File

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

View File

@@ -13,11 +13,25 @@ namespace ARMeilleure.Decoders
Cond = (Condition)((uint)opCode >> 28); Cond = (Condition)((uint)opCode >> 28);
} }
public bool IsThumb()
{
return this is OpCodeT16 || this is OpCodeT32;
}
public uint GetPc() public uint GetPc()
{ {
// Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline, // Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline,
// the PC actually points 2 instructions ahead. // 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 namespace ARMeilleure.Decoders
{ {
class OpCodeT16BImm11 : OpCode32, IOpCode32BImm class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm
{ {
public long Immediate { get; } public long Immediate { get; }

View File

@@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders namespace ARMeilleure.Decoders
{ {
class OpCodeT16BImm8 : OpCode32, IOpCode32BImm class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm
{ {
public long Immediate { get; } 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 ARMeilleure.Instructions;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics;
namespace ARMeilleure.Decoders namespace ARMeilleure.Decoders
{ {
@@ -972,8 +973,7 @@ namespace ARMeilleure.Decoders
SetA32("111100111x11<<10xxxx00011xx0xxxx", InstName.Vzip, InstEmit32.Vzip, OpCode32SimdCmpZ.Create); SetA32("111100111x11<<10xxxx00011xx0xxxx", InstName.Vzip, InstEmit32.Vzip, OpCode32SimdCmpZ.Create);
#endregion #endregion
#region "OpCode Table (AArch32, T16/T32)" #region "OpCode Table (AArch32, T16)"
// T16
SetT16("000<<xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftImm.Create); SetT16("000<<xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftImm.Create);
SetT16("0001100xxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubReg.Create); SetT16("0001100xxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubReg.Create);
SetT16("0001101xxxxxxxxx", InstName.Sub, InstEmit32.Sub, 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); SetT16("11100xxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT16BImm11.Create);
#endregion #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(InstA32FastLookup, AllInstA32, ToFastLookupIndexA);
FillFastLookupTable(InstT32FastLookup, AllInstT32, ToFastLookupIndexT); FillFastLookupTable(InstT32FastLookup, AllInstT32, ToFastLookupIndexT);
FillFastLookupTable(InstA64FastLookup, AllInstA64, ToFastLookupIndexA); FillFastLookupTable(InstA64FastLookup, AllInstA64, ToFastLookupIndexA);
@@ -1092,8 +1132,11 @@ namespace ARMeilleure.Decoders
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
{ {
encoding = encoding.Substring(16) + encoding.Substring(0, 16); string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
Set(encoding, AllInstT32, new InstDescriptor(name, emitter), makeOp); 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) private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)

View File

@@ -244,6 +244,23 @@ namespace ARMeilleure.Instructions
EmitAluStore(context, res); 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) public static void Pkh(ArmEmitterContext context)
{ {
OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp; OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp;

View File

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

View File

@@ -9,18 +9,25 @@ namespace ARMeilleure.Instructions
{ {
public static void Brk(ArmEmitterContext context) 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) public static void Svc(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
}
private static void EmitExceptionCall(ArmEmitterContext context, string name)
{ {
OpCodeException op = (OpCodeException)context.CurrOp; OpCodeException op = (OpCodeException)context.CurrOp;
string name = nameof(NativeInterface.SupervisorCall);
context.StoreToContext(); context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id)); 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.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.RawOpCode));
context.LoadFromContext(); context.LoadFromContext();
context.Return(Const(op.Address));
} }
} }
} }

View File

@@ -9,19 +9,11 @@ namespace ARMeilleure.Instructions
static partial class InstEmit32 static partial class InstEmit32
{ {
public static void Svc(ArmEmitterContext context) 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; IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
string name = nameof(NativeInterface.SupervisorCall);
context.StoreToContext(); context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id)); context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
@@ -30,5 +22,20 @@ namespace ARMeilleure.Instructions
Translator.EmitSynchronization(context); 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(); uint pc = op.GetPc();
bool isThumb = IsThumb(context.CurrOp); bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
uint currentPc = isThumb uint currentPc = isThumb
? pc | 1 ? pc | 1
@@ -61,7 +61,7 @@ namespace ARMeilleure.Instructions
Operand addr = context.Copy(GetIntA32(context, op.Rm)); Operand addr = context.Copy(GetIntA32(context, op.Rm));
Operand bitOne = context.BitwiseAnd(addr, Const(1)); Operand bitOne = context.BitwiseAnd(addr, Const(1));
bool isThumb = IsThumb(context.CurrOp); bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
uint currentPc = isThumb uint currentPc = isThumb
? (pc - 2) | 1 ? (pc - 2) | 1

View File

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

View File

@@ -43,6 +43,12 @@ namespace ARMeilleure.State
public long TpidrEl0 { get; set; } public long TpidrEl0 { get; set; }
public long Tpidr { get; set; } public long Tpidr { get; set; }
public uint Pstate
{
get => _nativeContext.GetPstate();
set => _nativeContext.SetPstate(value);
}
public FPCR Fpcr { get; set; } public FPCR Fpcr { get; set; }
public FPSR Fpsr { get; set; } public FPSR Fpsr { get; set; }
public FPCR StandardFpcrValue => (Fpcr & (FPCR.Ahp)) | FPCR.Dn | FPCR.Fz; 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; 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) public unsafe bool GetFPStateFlag(FPState flag)
{ {
if ((uint)flag >= RegisterConsts.FpFlagsCount) 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 OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\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 ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";

View File

@@ -149,11 +149,21 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
Span<byte> targetSpan = performanceOutput.Slice(nextOffset); 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]; ref THeader outputHeader = ref MemoryMarshal.Cast<byte, THeader>(targetSpan)[0];
nextOffset += Unsafe.SizeOf<THeader>(); 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; 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; int effectiveEntryDetailCount = 0;

View File

@@ -34,6 +34,7 @@ namespace Ryujinx.Common.Configuration
private const string DefaultModsDir = "mods"; private const string DefaultModsDir = "mods";
public static string CustomModsPath { get; set; } 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 CustomNandPath { get; set; } // TODO: Actually implement this into VFS
public static string CustomSdCardPath { get; set; } // TODO: Actually implement this into VFS public static string CustomSdCardPath { get; set; } // TODO: Actually implement this into VFS
@@ -85,5 +86,6 @@ namespace Ryujinx.Common.Configuration
} }
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 Stick Joystick { get; set; }
public bool InvertStickX { get; set; } public bool InvertStickX { get; set; }
public bool InvertStickY { get; set; } public bool InvertStickY { get; set; }
public bool Rotate90CW { get; set; }
public Button StickButton { 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 namespace Ryujinx.Common
{ {
// DO NOT EDIT, filled by CI // DO NOT EDIT, filled by CI
public static class ReleaseInformations public static class ReleaseInformations
{ {
private const string FlatHubChannelOwner = "flathub";
public static string BuildVersion = "%%RYUJINX_BUILD_VERSION%%"; public static string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
public static string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%"; public static string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
public static string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%"; public static string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
@@ -19,6 +23,11 @@ namespace Ryujinx.Common
!ReleaseChannelRepo.StartsWith("%%"); !ReleaseChannelRepo.StartsWith("%%");
} }
public static bool IsFlatHubBuild()
{
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
}
public static string GetVersion() public static string GetVersion()
{ {
if (IsValid()) if (IsValid())
@@ -30,5 +39,15 @@ namespace Ryujinx.Common
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion; return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
} }
} }
public static string GetBaseApplicationDirectory()
{
if (IsFlatHubBuild())
{
return AppDataManager.BaseDirPath;
}
return AppDomain.CurrentDomain.BaseDirectory;
}
} }
} }

View File

@@ -40,6 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly PhysicalMemory _physicalMemory; private readonly PhysicalMemory _physicalMemory;
private readonly MultiRangeList<Texture> _textures; private readonly MultiRangeList<Texture> _textures;
private readonly HashSet<Texture> _partiallyMappedTextures;
private Texture[] _textureOverlaps; private Texture[] _textureOverlaps;
private OverlapInfo[] _overlapInfo; private OverlapInfo[] _overlapInfo;
@@ -57,6 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_physicalMemory = physicalMemory; _physicalMemory = physicalMemory;
_textures = new MultiRangeList<Texture>(); _textures = new MultiRangeList<Texture>();
_partiallyMappedTextures = new HashSet<Texture>();
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; _textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
@@ -74,17 +76,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Texture[] overlaps = new Texture[10]; Texture[] overlaps = new Texture[10];
int overlapCount; int overlapCount;
MultiRange unmapped; MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
try
{
unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
}
catch (InvalidMemoryRegionException)
{
// This event fires on Map in case any mappings are overwritten. In that case, there may not be an existing mapping.
return;
}
lock (_textures) lock (_textures)
{ {
@@ -95,6 +87,24 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
overlaps[i].Unmapped(unmapped); overlaps[i].Unmapped(unmapped);
} }
// If any range was previously unmapped, we also need to purge
// all partially mapped texture, as they might be fully mapped now.
for (int i = 0; i < unmapped.Count; i++)
{
if (unmapped.GetSubRange(i).Address == MemoryManager.PteUnmapped)
{
lock (_partiallyMappedTextures)
{
foreach (var texture in _partiallyMappedTextures)
{
texture.Unmapped(unmapped);
}
}
break;
}
}
} }
/// <summary> /// <summary>
@@ -495,10 +505,20 @@ namespace Ryujinx.Graphics.Gpu.Image
SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize);
ulong size = (ulong)sizeInfo.TotalSize; ulong size = (ulong)sizeInfo.TotalSize;
bool partiallyMapped = false;
if (range == null) if (range == null)
{ {
range = memoryManager.GetPhysicalRegions(info.GpuAddress, size); range = memoryManager.GetPhysicalRegions(info.GpuAddress, size);
for (int i = 0; i < range.Value.Count; i++)
{
if (range.Value.GetSubRange(i).Address == MemoryManager.PteUnmapped)
{
partiallyMapped = true;
break;
}
}
} }
// Find view compatible matches. // Find view compatible matches.
@@ -784,6 +804,14 @@ namespace Ryujinx.Graphics.Gpu.Image
_textures.Add(texture); _textures.Add(texture);
} }
if (partiallyMapped)
{
lock (_partiallyMappedTextures)
{
_partiallyMappedTextures.Add(texture);
}
}
ShrinkOverlapsBufferIfNeeded(); ShrinkOverlapsBufferIfNeeded();
for (int i = 0; i < overlapsCount; i++) for (int i = 0; i < overlapsCount; i++)
@@ -1079,6 +1107,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
_textures.Remove(texture); _textures.Remove(texture);
} }
lock (_partiallyMappedTextures)
{
_partiallyMappedTextures.Remove(texture);
}
} }
/// <summary> /// <summary>

View File

@@ -879,7 +879,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int sliceStart = Math.Clamp(offset, 0, subRangeSize); int sliceStart = Math.Clamp(offset, 0, subRangeSize);
int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize); int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize);
if (sliceStart != sliceEnd) if (sliceStart != sliceEnd && item.Address != MemoryManager.PteUnmapped)
{ {
result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart))); result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart)));
} }
@@ -1097,11 +1097,20 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
// Single dirty region. // Single dirty region.
var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count]; var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count];
int count = 0;
for (int i = 0; i < TextureRange.Count; i++) for (int i = 0; i < TextureRange.Count; i++)
{ {
var currentRange = TextureRange.GetSubRange(i); var currentRange = TextureRange.GetSubRange(i);
cpuRegionHandles[i] = GenerateHandle(currentRange.Address, currentRange.Size); if (currentRange.Address != MemoryManager.PteUnmapped)
{
cpuRegionHandles[count++] = GenerateHandle(currentRange.Address, currentRange.Size);
}
}
if (count != TextureRange.Count)
{
Array.Resize(ref cpuRegionHandles, count);
} }
var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles); var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles);

View File

@@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
private const ulong BufferAlignmentSize = 0x1000; private const ulong BufferAlignmentSize = 0x1000;
private const ulong BufferAlignmentMask = BufferAlignmentSize - 1; private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
private const ulong MaxDynamicGrowthSize = 0x100000;
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly PhysicalMemory _physicalMemory; 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 // 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 // that fits all the data we need. We also need to copy the contents from the
// old buffer(s) to the new buffer. // old buffer(s) to the new buffer.
ulong endAddress = address + size; ulong endAddress = address + size;
if (_bufferOverlaps[0].Address > address || _bufferOverlaps[0].EndAddress < endAddress) 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++) for (int index = 0; index < overlapsCount; index++)
{ {
Buffer buffer = _bufferOverlaps[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) lock (_buffers)
{ {
@@ -202,7 +231,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
buffer.DisposeData(); buffer.DisposeData();
} }
newBuffer.SynchronizeMemory(address, endAddress - address); newBuffer.SynchronizeMemory(address, newSize);
// Existing buffers were modified, we need to rebind everything. // Existing buffers were modified, we need to rebind everything.
NotifyBuffersModified?.Invoke(); NotifyBuffersModified?.Invoke();

View File

@@ -28,7 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private const int PtLvl1Bit = PtPageBits; private const int PtLvl1Bit = PtPageBits;
private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits; private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits;
public const ulong PteUnmapped = 0xffffffff_ffffffff; public const ulong PteUnmapped = ulong.MaxValue;
private readonly ulong[][] _pageTable; private readonly ulong[][] _pageTable;
@@ -340,7 +340,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="va">Virtual address of the range</param> /// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param> /// <param name="size">Size of the range</param>
/// <returns>Multi-range with the physical regions</returns> /// <returns>Multi-range with the physical regions</returns>
/// <exception cref="InvalidMemoryRegionException">The memory region specified by <paramref name="va"/> and <paramref name="size"/> is not fully mapped</exception>
public MultiRange GetPhysicalRegions(ulong va, ulong size) public MultiRange GetPhysicalRegions(ulong va, ulong size)
{ {
if (IsContiguous(va, (int)size)) if (IsContiguous(va, (int)size))
@@ -348,11 +347,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
return new MultiRange(Translate(va), size); return new MultiRange(Translate(va), size);
} }
if (!IsMapped(va))
{
throw new InvalidMemoryRegionException($"The specified GPU virtual address 0x{va:X} is not mapped.");
}
ulong regionStart = Translate(va); ulong regionStart = Translate(va);
ulong regionSize = Math.Min(size, PageSize - (va & PageMask)); ulong regionSize = Math.Min(size, PageSize - (va & PageMask));
@@ -367,14 +361,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int page = 0; page < pages - 1; page++) for (int page = 0; page < pages - 1; page++)
{ {
if (!IsMapped(va + PageSize)) ulong currPa = Translate(va);
{
throw new InvalidMemoryRegionException($"The specified GPU virtual memory range 0x{va:X}..0x{(va + size):X} is not fully mapped.");
}
ulong newPa = Translate(va + PageSize); ulong newPa = Translate(va + PageSize);
if (Translate(va) + PageSize != newPa) if ((currPa != PteUnmapped || newPa != PteUnmapped) && currPa + PageSize != newPa)
{ {
regions.Add(new MemoryRange(regionStart, regionSize)); regions.Add(new MemoryRange(regionStart, regionSize));
regionStart = newPa; regionStart = newPa;
@@ -405,6 +395,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
MemoryRange currentRange = range.GetSubRange(i); MemoryRange currentRange = range.GetSubRange(i);
if (currentRange.Address != PteUnmapped)
{
ulong address = currentRange.Address & ~PageMask; ulong address = currentRange.Address & ~PageMask;
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask; ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
@@ -419,6 +411,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
address += PageSize; address += PageSize;
} }
} }
else
{
ulong endVa = va + (((currentRange.Size) + PageMask) & ~PageMask);
while (va < endVa)
{
if (Translate(va) != PteUnmapped)
{
return false;
}
va += PageSize;
}
}
}
return true; return true;
} }

View File

@@ -7,8 +7,6 @@ using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
@@ -19,8 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
class PhysicalMemory : IDisposable class PhysicalMemory : IDisposable
{ {
public const int PageSize = 0x1000;
private readonly GpuContext _context; private readonly GpuContext _context;
private IVirtualMemoryManagerTracked _cpuMemory; private IVirtualMemoryManagerTracked _cpuMemory;
private int _referenceCount; private int _referenceCount;
@@ -103,10 +99,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (range.Count == 1) if (range.Count == 1)
{ {
var singleRange = range.GetSubRange(0); var singleRange = range.GetSubRange(0);
if (singleRange.Address != MemoryManager.PteUnmapped)
{
return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked); return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
} }
else }
{
Span<byte> data = new byte[range.GetSize()]; Span<byte> data = new byte[range.GetSize()];
int offset = 0; int offset = 0;
@@ -115,13 +113,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
var currentRange = range.GetSubRange(i); var currentRange = range.GetSubRange(i);
int size = (int)currentRange.Size; int size = (int)currentRange.Size;
if (currentRange.Address != MemoryManager.PteUnmapped)
{
_cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size)); _cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size));
}
offset += size; offset += size;
} }
return data; return data;
} }
}
/// <summary> /// <summary>
/// Gets a writable region from the application process. /// Gets a writable region from the application process.
@@ -156,11 +156,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
int offset = 0; int offset = 0;
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
{ {
MemoryRange subrange = range.GetSubRange(i); var currentRange = range.GetSubRange(i);
int size = (int)currentRange.Size;
GetSpan(subrange.Address, (int)subrange.Size).CopyTo(memory.Span.Slice(offset, (int)subrange.Size)); if (currentRange.Address != MemoryManager.PteUnmapped)
{
offset += (int)subrange.Size; GetSpan(currentRange.Address, size).CopyTo(memory.Span.Slice(offset, size));
}
offset += size;
} }
return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked); return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked);
@@ -253,8 +255,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (range.Count == 1) if (range.Count == 1)
{ {
var singleRange = range.GetSubRange(0); var singleRange = range.GetSubRange(0);
if (singleRange.Address != MemoryManager.PteUnmapped)
{
writeCallback(singleRange.Address, data); writeCallback(singleRange.Address, data);
} }
}
else else
{ {
int offset = 0; int offset = 0;
@@ -263,7 +268,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
var currentRange = range.GetSubRange(i); var currentRange = range.GetSubRange(i);
int size = (int)currentRange.Size; int size = (int)currentRange.Size;
if (currentRange.Address != MemoryManager.PteUnmapped)
{
writeCallback(currentRange.Address, data.Slice(offset, size)); writeCallback(currentRange.Address, data.Slice(offset, size));
}
offset += size; offset += size;
} }
} }
@@ -288,11 +296,20 @@ namespace Ryujinx.Graphics.Gpu.Memory
public GpuRegionHandle BeginTracking(MultiRange range) public GpuRegionHandle BeginTracking(MultiRange range)
{ {
var cpuRegionHandles = new CpuRegionHandle[range.Count]; var cpuRegionHandles = new CpuRegionHandle[range.Count];
int count = 0;
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
{ {
var currentRange = range.GetSubRange(i); var currentRange = range.GetSubRange(i);
cpuRegionHandles[i] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size); if (currentRange.Address != MemoryManager.PteUnmapped)
{
cpuRegionHandles[count++] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size);
}
}
if (count != range.Count)
{
Array.Resize(ref cpuRegionHandles, count);
} }
return new GpuRegionHandle(cpuRegionHandles); return new GpuRegionHandle(cpuRegionHandles);

View File

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

View File

@@ -308,7 +308,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
int attr = offset + elemIndex * 4; int attr = offset + elemIndex * 4;
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd) if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
{ {
int index = (attr - AttributeConsts.UserAttributeBase) / 16; int userAttr = attr - AttributeConsts.UserAttributeBase;
int index = userAttr / 16;
if (isStore) if (isStore)
{ {
@@ -316,7 +317,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
} }
else 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 _nextUsedInputAttributes;
private int _thisUsedInputAttributes; 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 _usedConstantBuffers;
private int _usedStorageBuffers; private int _usedStorageBuffers;
private int _usedStorageBuffersWrite; private int _usedStorageBuffersWrite;
@@ -227,11 +232,12 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedOutputAttributes |= 1 << index; UsedOutputAttributes |= 1 << index;
} }
public void SetInputUserAttribute(int index, bool perPatch) public void SetInputUserAttribute(int index, int component, bool perPatch)
{ {
if (perPatch) if (perPatch)
{ {
UsedInputAttributesPerPatch |= 1 << index; UsedInputAttributesPerPatch |= 1 << index;
ThisInputAttributesPerPatchComponents |= UInt128.Pow2(index * 4 + component);
} }
else else
{ {
@@ -239,6 +245,7 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedInputAttributes |= mask; UsedInputAttributes |= mask;
_thisUsedInputAttributes |= mask; _thisUsedInputAttributes |= mask;
ThisInputAttributesComponents |= UInt128.Pow2(index * 4 + component);
} }
} }
@@ -256,6 +263,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public void MergeFromtNextStage(ShaderConfig config) public void MergeFromtNextStage(ShaderConfig config)
{ {
NextInputAttributesComponents = config.ThisInputAttributesComponents;
NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents;
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr); NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch); MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
} }
@@ -319,6 +328,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public void SetAllInputUserAttributes() public void SetAllInputUserAttributes()
{ {
UsedInputAttributes |= Constants.AllAttributesMask; UsedInputAttributes |= Constants.AllAttributesMask;
ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4);
} }
public void SetAllOutputUserAttributes() public void SetAllOutputUserAttributes()

View File

@@ -214,24 +214,31 @@ namespace Ryujinx.Graphics.Shader.Translation
InitializeOutput(context, AttributeConsts.PositionX, perPatch: false); InitializeOutput(context, AttributeConsts.PositionX, perPatch: false);
} }
int usedAttributes = context.Config.UsedOutputAttributes; UInt128 usedAttributes = context.Config.NextInputAttributesComponents;
while (usedAttributes != 0) 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;
} }
int usedAttributesPerPatch = context.Config.UsedOutputAttributesPerPatch; InitializeOutputComponent(context, AttributeConsts.UserAttributeBase + index * 4, perPatch: false);
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) 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) private static void EmitOps(EmitterContext context, Block block)
{ {
for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++) 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); 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) if (TitleId != 0)
{ {
@@ -388,7 +391,10 @@ namespace Ryujinx.HLE.HOS
MetaLoader metaData = ReadNpdm(codeFs); 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) if (controlNca != null)
{ {
@@ -481,14 +487,14 @@ namespace Ryujinx.HLE.HOS
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length) 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)) 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 else
@@ -615,20 +621,20 @@ namespace Ryujinx.HLE.HOS
ref ApplicationControlProperty nacp = ref ControlData.Value; 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)) 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) if (nacp.PresenceGroupId != 0)
{ {
programInfo.ProgramId = nacp.PresenceGroupId; 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) 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. // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
control.UserAccountSaveDataSize = 0x4000; control.UserAccountSaveDataSize = 0x4000;
control.UserAccountSaveDataJournalSize = 0x4000; control.UserAccountSaveDataJournalSize = 0x4000;
control.SaveDataOwnerId = applicationId; control.SaveDataOwnerId = applicationId.Value;
Logger.Warning?.Print(LogClass.Application, 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."); "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; 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()) if (resultCode.IsFailure())
{ {
@@ -792,7 +798,7 @@ namespace Ryujinx.HLE.HOS
return resultCode; 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()) if (resultCode.IsFailure())
{ {

View File

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

View File

@@ -751,7 +751,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); KThread currentThread = KernelStatic.GetCurrentThread();
if (currentThread.Owner != null && if (currentThread.Context.Running &&
currentThread.Owner != null &&
currentThread.GetUserDisableCount() != 0 && currentThread.GetUserDisableCount() != 0 &&
currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null) 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) private static uint GetPsr(ARMeilleure.State.ExecutionContext context)
{ {
return (context.GetPstateFlag(ARMeilleure.State.PState.NFlag) ? (1U << (int)ARMeilleure.State.PState.NFlag) : 0U) | return context.Pstate & 0xFF0FFE20;
(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);
} }
private ThreadContext GetCurrentContext() private ThreadContext GetCurrentContext()

View File

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

View File

@@ -137,6 +137,7 @@ namespace Ryujinx.HLE.HOS
private static bool StrEquals(string s1, string s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase); 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) private string EnsureBaseDirStructure(string modsBasePath)
{ {
@@ -695,7 +696,7 @@ namespace Ryujinx.HLE.HOS
var buildIds = programs.Select(p => p switch 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'), NroExecutable nro => BitConverter.ToString(nro.Header.BuildId).Replace("-", "").TrimEnd('0'),
_ => string.Empty _ => string.Empty
}).ToList(); }).ToList();

View File

@@ -160,7 +160,7 @@ namespace Ryujinx.HLE.HOS
var buildIds = executables.Select(e => (e switch 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), NroExecutable nro => BitConverter.ToString(nro.Header.BuildId),
_ => "" _ => ""
}).Replace("-", "").ToUpper()); }).Replace("-", "").ToUpper());

View File

@@ -3,6 +3,7 @@ using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@@ -25,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
public UserProfile LastOpenedUser { get; private set; } public UserProfile LastOpenedUser { get; private set; }
public AccountManager(HorizonClient horizonClient) public AccountManager(HorizonClient horizonClient, string initialProfileName = null)
{ {
_horizonClient = horizonClient; _horizonClient = horizonClient;
@@ -43,7 +44,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
} }
else 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) private void DeleteSaveData(UserId userId)
{ {
SaveDataFilter saveDataFilter = new SaveDataFilter(); var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: default,
saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low)); new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low), saveDataId: default, index: default);
using var saveDataIterator = new UniqueRef<SaveDataIterator>(); 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. // 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. // 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); 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; 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); 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 // TODO: When above calls are implemented, switch to using ns:am
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode; 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); int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
if (firstSupported > (int)SystemState.TitleLanguage.BrazilianPortuguese) if (firstSupported > (int)SystemState.TitleLanguage.BrazilianPortuguese)
@@ -190,7 +190,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// GetDisplayVersion() -> nn::oe::DisplayVersion // GetDisplayVersion() -> nn::oe::DisplayVersion
public ResultCode GetDisplayVersion(ServiceCtx context) 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. // 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); 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; BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize, 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); journalSize);
if (result.IsFailure()) return (ResultCode)result.Value; if (result.IsFailure()) return (ResultCode)result.Value;

View File

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

View File

@@ -132,23 +132,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
} }
} }
public static Result ReadFsPath(out FsPath path, ServiceCtx context, int index = 0) public static ref readonly FspPath GetFspPath(ServiceCtx context, int index = 0)
{ {
ulong position = context.Request.PtrBuff[index].Position; ulong position = context.Request.PtrBuff[index].Position;
ulong size = context.Request.PtrBuff[index].Size; 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;
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size); ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
ReadOnlySpan<FspPath> fspBuffer = MemoryMarshal.Cast<byte, FspPath>(buffer); 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) public static ref readonly LibHac.FsSrv.Sf.Path GetSfPath(ServiceCtx context, int index = 0)
{ {
ulong position = (ulong)context.Request.PtrBuff[index].Position; ulong position = context.Request.PtrBuff[index].Position;
ulong size = (ulong)context.Request.PtrBuff[index].Size; ulong size = context.Request.PtrBuff[index].Size;
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size); ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
ReadOnlySpan<LibHac.FsSrv.Sf.Path> pathBuffer = MemoryMarshal.Cast<byte, LibHac.FsSrv.Sf.Path>(buffer); ReadOnlySpan<LibHac.FsSrv.Sf.Path> pathBuffer = MemoryMarshal.Cast<byte, LibHac.FsSrv.Sf.Path>(buffer);

View File

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

View File

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

View File

@@ -1,8 +1,9 @@
using LibHac.Ns;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Arp; using Ryujinx.HLE.HOS.Services.Arp;
using System; using System;
using static LibHac.Ns.ApplicationControlProperty;
namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
{ {
class IParentalControlService : IpcService class IParentalControlService : IpcService
@@ -52,8 +53,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
_titleId = titleId; _titleId = titleId;
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields. // 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); _ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
_parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControl; _parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag;
} }
} }
@@ -224,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
private ResultCode IsStereoVisionPermittedImpl() 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. // Since we don't support the pctl savedata for now, this can be implemented later.
if (appExemption) 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) 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 inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size; 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>(); 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. // Check if input title ids are in the whitelist.
foreach (ulong titleId in titleIds) 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; return (ResultCode)Am.ResultCode.ObjectInvalid;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Concentus" Version="1.1.7" /> <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="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />

View File

@@ -220,6 +220,7 @@ namespace Ryujinx.Headless.SDL2
StickButton = ConfigGamepadInputId.LeftStick, StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false, InvertStickX = false,
InvertStickY = false, InvertStickY = false,
Rotate90CW = false,
}, },
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
@@ -241,6 +242,7 @@ namespace Ryujinx.Headless.SDL2
StickButton = ConfigGamepadInputId.RightStick, StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false, InvertStickX = false,
InvertStickY = false, InvertStickY = false,
Rotate90CW = false,
}, },
Motion = new StandardMotionConfigController Motion = new StandardMotionConfigController
@@ -396,7 +398,7 @@ namespace Ryujinx.Headless.SDL2
if ((bool)option.EnableFileLog) if ((bool)option.EnableFileLog)
{ {
Logger.AddTarget(new AsyncLogTargetWrapper( Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(AppDomain.CurrentDomain.BaseDirectory, "file"), new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
1000, 1000,
AsyncLogTargetOverflowAction.Block AsyncLogTargetOverflowAction.Block
)); ));

View File

@@ -350,6 +350,14 @@ namespace Ryujinx.Input.SDL2
{ {
resultY = -resultY; 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); return (resultX, resultY);

View File

@@ -50,6 +50,13 @@ namespace Ryujinx.Memory.Range
ulong otherAddress = other.Address; ulong otherAddress = other.Address;
ulong otherEndAddress = other.EndAddress; ulong otherEndAddress = other.EndAddress;
// If any of the ranges if invalid (address + size overflows),
// then they are never considered to overlap.
if (thisEndAddress < thisAddress || otherEndAddress < otherAddress)
{
return false;
}
return thisAddress < otherEndAddress && otherAddress < thisEndAddress; return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
} }

View File

@@ -29,6 +29,12 @@ namespace Ryujinx.Memory.Range
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
{ {
var subrange = range.GetSubRange(i); var subrange = range.GetSubRange(i);
if (IsInvalid(ref subrange))
{
continue;
}
_items.Add(subrange.Address, subrange.EndAddress, item); _items.Add(subrange.Address, subrange.EndAddress, item);
} }
@@ -49,6 +55,12 @@ namespace Ryujinx.Memory.Range
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
{ {
var subrange = range.GetSubRange(i); var subrange = range.GetSubRange(i);
if (IsInvalid(ref subrange))
{
continue;
}
removed += _items.Remove(subrange.Address, item); removed += _items.Remove(subrange.Address, item);
} }
@@ -86,6 +98,12 @@ namespace Ryujinx.Memory.Range
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
{ {
var subrange = range.GetSubRange(i); var subrange = range.GetSubRange(i);
if (IsInvalid(ref subrange))
{
continue;
}
overlapCount = _items.Get(subrange.Address, subrange.EndAddress, ref output, overlapCount); overlapCount = _items.Get(subrange.Address, subrange.EndAddress, ref output, overlapCount);
} }
@@ -124,6 +142,17 @@ namespace Ryujinx.Memory.Range
return overlapCount; return overlapCount;
} }
/// <summary>
/// Checks if a given sub-range of memory is invalid.
/// Those are used to represent unmapped memory regions (holes in the region mapping).
/// </summary>
/// <param name="subRange">Memory range to checl</param>
/// <returns>True if the memory range is considered invalid, false otherwise</returns>
private static bool IsInvalid(ref MemoryRange subRange)
{
return subRange.Address == ulong.MaxValue;
}
/// <summary> /// <summary>
/// Gets all items on the list starting at the specified memory address. /// Gets all items on the list starting at the specified memory address.
/// </summary> /// </summary>

View File

@@ -1,4 +1,5 @@
using Ryujinx.Common.Logging; using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@@ -81,7 +82,7 @@ namespace Ryujinx.SDL2.Common
SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE); 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)) if (File.Exists(gamepadDbPath))
{ {

View File

@@ -257,6 +257,35 @@ namespace Ryujinx.Tests.Cpu
return GetContext(); 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) protected void SetWorkingMemory(uint offset, byte[] data)
{ {
_memory.Write(DataBaseAddress + offset, 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> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 36; public const int CurrentVersion = 37;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format
@@ -236,6 +236,11 @@ namespace Ryujinx.Configuration
/// </summary> /// </summary>
public bool StartFullscreen { get; set; } public bool StartFullscreen { get; set; }
/// <summary>
/// Show console window
/// </summary>
public bool ShowConsole { get; set; }
/// <summary> /// <summary>
/// Enable or disable keyboard support (Independent from controllers binding) /// Enable or disable keyboard support (Independent from controllers binding)
/// </summary> /// </summary>

View File

@@ -6,6 +6,7 @@ using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Configuration.System; using Ryujinx.Configuration.System;
using Ryujinx.Configuration.Ui; using Ryujinx.Configuration.Ui;
using Ryujinx.Ui.Helper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -88,6 +89,11 @@ namespace Ryujinx.Configuration
/// </summary> /// </summary>
public ReactiveObject<bool> StartFullscreen { get; private set; } public ReactiveObject<bool> StartFullscreen { get; private set; }
/// <summary>
/// Hide / Show Console Window
/// </summary>
public ReactiveObject<bool> ShowConsole { get; private set; }
public UiSection() public UiSection()
{ {
GuiColumns = new Columns(); GuiColumns = new Columns();
@@ -96,6 +102,8 @@ namespace Ryujinx.Configuration
EnableCustomTheme = new ReactiveObject<bool>(); EnableCustomTheme = new ReactiveObject<bool>();
CustomThemePath = new ReactiveObject<string>(); CustomThemePath = new ReactiveObject<string>();
StartFullscreen = new ReactiveObject<bool>(); 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, EnableCustomTheme = Ui.EnableCustomTheme,
CustomThemePath = Ui.CustomThemePath, CustomThemePath = Ui.CustomThemePath,
StartFullscreen = Ui.StartFullscreen, StartFullscreen = Ui.StartFullscreen,
ShowConsole = Ui.ShowConsole,
EnableKeyboard = Hid.EnableKeyboard, EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse, EnableMouse = Hid.EnableMouse,
Hotkeys = Hid.Hotkeys, Hotkeys = Hid.Hotkeys,
@@ -574,6 +583,7 @@ namespace Ryujinx.Configuration
Ui.EnableCustomTheme.Value = false; Ui.EnableCustomTheme.Value = false;
Ui.CustomThemePath.Value = ""; Ui.CustomThemePath.Value = "";
Ui.StartFullscreen.Value = false; Ui.StartFullscreen.Value = false;
Ui.ShowConsole.Value = true;
Hid.EnableKeyboard.Value = false; Hid.EnableKeyboard.Value = false;
Hid.EnableMouse.Value = false; Hid.EnableMouse.Value = false;
Hid.Hotkeys.Value = new KeyboardHotkeys Hid.Hotkeys.Value = new KeyboardHotkeys
@@ -1008,6 +1018,15 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true; 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; Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading; Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading;
Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScale.Value = configurationFileFormat.ResScale;
@@ -1061,6 +1080,7 @@ namespace Ryujinx.Configuration
Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme; Ui.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme;
Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath; Ui.CustomThemePath.Value = configurationFileFormat.CustomThemePath;
Ui.StartFullscreen.Value = configurationFileFormat.StartFullscreen; Ui.StartFullscreen.Value = configurationFileFormat.StartFullscreen;
Ui.ShowConsole.Value = configurationFileFormat.ShowConsole;
Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard; Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard;
Hid.EnableMouse.Value = configurationFileFormat.EnableMouse; Hid.EnableMouse.Value = configurationFileFormat.EnableMouse;
Hid.Hotkeys.Value = configurationFileFormat.Hotkeys; Hid.Hotkeys.Value = configurationFileFormat.Hotkeys;

View File

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

View File

@@ -311,7 +311,7 @@ namespace Ryujinx.Modules
catch (Exception e) catch (Exception e)
{ {
Logger.Warning?.Print(LogClass.Application, e.Message); 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); DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
@@ -327,7 +327,7 @@ namespace Ryujinx.Modules
catch (WebException ex) catch (WebException ex)
{ {
Logger.Warning?.Print(LogClass.Application, ex.Message); 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++) for (int j = 0; j < webClients.Count; j++)
{ {
@@ -566,9 +566,16 @@ namespace Ryujinx.Modules
return true; return true;
#else #else
if (showWarnings) if (showWarnings)
{
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."); GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
} }
}
return false; return false;
#endif #endif

View File

@@ -27,6 +27,8 @@ namespace Ryujinx
public static string ConfigurationPath { get; set; } public static string ConfigurationPath { get; set; }
public static string CommandLineProfile { get; set; }
[DllImport("libX11")] [DllImport("libX11")]
private extern static int XInitThreads(); private extern static int XInitThreads();
@@ -52,6 +54,17 @@ namespace Ryujinx
baseDirPathArg = args[++i]; 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") else if (arg == "-f" || arg == "--fullscreen")
{ {
startFullscreenArg = true; startFullscreenArg = true;

View File

@@ -558,10 +558,10 @@ namespace Ryujinx.Ui.App
{ {
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage); _ = 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(); titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
publisher = controlData.Titles[(int)desiredTitleLanguage].Publisher.ToString(); publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
} }
else else
{ {
@@ -571,11 +571,11 @@ namespace Ryujinx.Ui.App
if (string.IsNullOrWhiteSpace(titleName)) 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; break;
} }
@@ -584,11 +584,11 @@ namespace Ryujinx.Ui.App
if (string.IsNullOrWhiteSpace(publisher)) 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; break;
} }
@@ -599,7 +599,7 @@ namespace Ryujinx.Ui.App
{ {
titleId = controlData.PresenceGroupId.ToString("x16"); titleId = controlData.PresenceGroupId.ToString("x16");
} }
else if (controlData.SaveDataOwnerId.Value != 0) else if (controlData.SaveDataOwnerId != 0)
{ {
titleId = controlData.SaveDataOwnerId.ToString(); titleId = controlData.SaveDataOwnerId.ToString();
} }
@@ -612,7 +612,7 @@ namespace Ryujinx.Ui.App
titleId = "0000000000000000"; titleId = "0000000000000000";
} }
version = controlData.DisplayVersion.ToString(); version = controlData.DisplayVersionString.ToString();
} }
private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs) 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 _hideUi;
[GUI] MenuItem _fullScreen; [GUI] MenuItem _fullScreen;
[GUI] CheckMenuItem _startFullScreen; [GUI] CheckMenuItem _startFullScreen;
[GUI] CheckMenuItem _showConsole;
[GUI] CheckMenuItem _favToggle; [GUI] CheckMenuItem _favToggle;
[GUI] MenuItem _firmwareInstallDirectory; [GUI] MenuItem _firmwareInstallDirectory;
[GUI] MenuItem _firmwareInstallFile; [GUI] MenuItem _firmwareInstallFile;
@@ -178,7 +179,7 @@ namespace Ryujinx.Ui
VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient); VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient);
_contentManager = new ContentManager(_virtualFileSystem); _contentManager = new ContentManager(_virtualFileSystem);
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient); _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, Program.CommandLineProfile);
_userChannelPersistence = new UserChannelPersistence(); _userChannelPersistence = new UserChannelPersistence();
// Instantiate GUI objects. // Instantiate GUI objects.
@@ -213,6 +214,9 @@ namespace Ryujinx.Ui
_startFullScreen.Active = true; _startFullScreen.Active = true;
} }
_showConsole.Active = ConfigurationState.Instance.Ui.ShowConsole.Value;
_showConsole.Visible = ConsoleHelper.SetConsoleWindowStateSupported;
_actionMenu.Sensitive = false; _actionMenu.Sensitive = false;
_pauseEmulation.Sensitive = false; _pauseEmulation.Sensitive = false;
_resumeEmulation.Sensitive = false; _resumeEmulation.Sensitive = false;
@@ -1291,7 +1295,7 @@ namespace Ryujinx.Ui
private void OpenLogsFolder_Pressed(object sender, EventArgs args) 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(); new DirectoryInfo(logPath).Create();
@@ -1535,6 +1539,13 @@ namespace Ryujinx.Ui
SaveConfig(); 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) private void OptionMenu_StateChanged(object o, StateChangedArgs args)
{ {
_manageUserProfiles.Sensitive = _emulationContext == null; _manageUserProfiles.Sensitive = _emulationContext == null;

View File

@@ -142,6 +142,15 @@
<signal name="toggled" handler="StartFullScreen_Toggled" swapped="no"/> <signal name="toggled" handler="StartFullScreen_Toggled" swapped="no"/>
</object> </object>
</child> </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> <child>
<object class="GtkSeparatorMenuItem"> <object class="GtkSeparatorMenuItem">
<property name="visible">True</property> <property name="visible">True</property>

View File

@@ -11,6 +11,7 @@ namespace Ryujinx.Ui.Widgets
private MenuItem _manageDlcMenuItem; private MenuItem _manageDlcMenuItem;
private MenuItem _manageCheatMenuItem; private MenuItem _manageCheatMenuItem;
private MenuItem _openTitleModDirMenuItem; private MenuItem _openTitleModDirMenuItem;
private MenuItem _openTitleSdModDirMenuItem;
private Menu _extractSubMenu; private Menu _extractSubMenu;
private MenuItem _extractMenuItem; private MenuItem _extractMenuItem;
private MenuItem _extractRomFsMenuItem; private MenuItem _extractRomFsMenuItem;
@@ -88,6 +89,15 @@ namespace Ryujinx.Ui.Widgets
}; };
_openTitleModDirMenuItem.Activated += OpenTitleModDir_Clicked; _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 // _extractSubMenu
// //
@@ -199,6 +209,7 @@ namespace Ryujinx.Ui.Widgets
Add(_manageDlcMenuItem); Add(_manageDlcMenuItem);
Add(_manageCheatMenuItem); Add(_manageCheatMenuItem);
Add(_openTitleModDirMenuItem); Add(_openTitleModDirMenuItem);
Add(_openTitleSdModDirMenuItem);
Add(new SeparatorMenuItem()); Add(new SeparatorMenuItem());
Add(_manageCacheMenuItem); Add(_manageCacheMenuItem);
Add(_extractMenuItem); Add(_extractMenuItem);

View File

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

View File

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

View File

@@ -740,6 +740,19 @@
<property name="top_attach">1</property> <property name="top_attach">1</property>
</packing> </packing>
</child> </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> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@@ -1697,6 +1710,19 @@
<property name="top_attach">1</property> <property name="top_attach">1</property>
</packing> </packing>
</child> </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> </object>
<packing> <packing>
<property name="expand">False</property> <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(); 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(); 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); radioButton.JoinGroup(_noUpdateRadioButton);
_availableUpdatesBox.Add(radioButton); _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