Merge branch 'Ryujinx:master' into master

This commit is contained in:
Logan Stromberg 2022-03-15 17:57:25 -07:00 committed by GitHub
commit b277ac579f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
114 changed files with 3091 additions and 495 deletions

View file

@ -18,7 +18,7 @@ namespace ARMeilleure.Decoders
// For lower code quality translation, we set a lower limit since we're blocking execution. // For lower code quality translation, we set a lower limit since we're blocking execution.
private const int MaxInstsPerFunctionLowCq = 500; private const int MaxInstsPerFunctionLowCq = 500;
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, bool singleBlock) public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, DecoderMode dMode)
{ {
List<Block> blocks = new List<Block>(); List<Block> blocks = new List<Block>();
@ -38,7 +38,7 @@ namespace ARMeilleure.Decoders
{ {
block = new Block(blkAddress); block = new Block(blkAddress);
if ((singleBlock && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress)) if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
{ {
block.Exit = true; block.Exit = true;
block.EndAddress = blkAddress; block.EndAddress = blkAddress;
@ -96,6 +96,12 @@ namespace ARMeilleure.Decoders
} }
} }
if (dMode == DecoderMode.SingleInstruction)
{
// Only read at most one instruction
limitAddress = currBlock.Address + 1;
}
FillBlock(memory, mode, currBlock, limitAddress); FillBlock(memory, mode, currBlock, limitAddress);
opsCount += currBlock.OpCodes.Count; opsCount += currBlock.OpCodes.Count;
@ -115,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);
} }
@ -143,7 +149,7 @@ namespace ARMeilleure.Decoders
throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}."); throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}.");
} }
if (!singleBlock) if (dMode == DecoderMode.MultipleBlocks)
{ {
return TailCallRemover.RunPass(address, blocks); return TailCallRemover.RunPass(address, blocks);
} }
@ -257,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;
} }
@ -318,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

@ -0,0 +1,9 @@
namespace ARMeilleure.Decoders
{
enum DecoderMode
{
MultipleBlocks,
SingleBlock,
SingleInstruction,
}
}

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
@ -71,7 +71,7 @@ namespace ARMeilleure.Instructions
SetFlag(context, PState.TFlag, bitOne); SetFlag(context, PState.TFlag, bitOne);
EmitVirtualCall(context, addr); EmitBxWritePc(context, addr);
} }
public static void Bx(ArmEmitterContext context) public static void Bx(ArmEmitterContext context)

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);
@ -186,7 +181,7 @@ namespace ARMeilleure.Instructions
SetFlag(context, PState.TFlag, mode); SetFlag(context, PState.TFlag, mode);
Operand addr = context.ConditionalSelect(mode, pc, context.BitwiseAnd(pc, Const(~3))); Operand addr = context.ConditionalSelect(mode, context.BitwiseAnd(pc, Const(~1)), context.BitwiseAnd(pc, Const(~3)));
InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn); InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn);
} }

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

@ -113,7 +113,7 @@ namespace ARMeilleure.Translation
} }
} }
Array.Clear(localDefs, 0, localDefs.Length); Array.Clear(localDefs);
} }
// Second pass, rename variables with definitions on different blocks. // Second pass, rename variables with definitions on different blocks.

View file

@ -209,6 +209,17 @@ namespace ARMeilleure.Translation
return nextAddr; return nextAddr;
} }
public ulong Step(State.ExecutionContext context, ulong address)
{
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
address = func.Execute(context);
EnqueueForDeletion(address, func);
return address;
}
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
{ {
if (!Functions.TryGetValue(address, out TranslatedFunction func)) if (!Functions.TryGetValue(address, out TranslatedFunction func))
@ -242,7 +253,7 @@ namespace ARMeilleure.Translation
} }
} }
internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq) internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false)
{ {
var context = new ArmEmitterContext( var context = new ArmEmitterContext(
Memory, Memory,
@ -255,7 +266,7 @@ namespace ARMeilleure.Translation
Logger.StartPass(PassName.Decoding); Logger.StartPass(PassName.Decoding);
Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleBlock: false); Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleStep ? DecoderMode.SingleInstruction : DecoderMode.MultipleBlocks);
Logger.EndPass(PassName.Decoding); Logger.EndPass(PassName.Decoding);
@ -285,14 +296,14 @@ namespace ARMeilleure.Translation
var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None; var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
if (context.HasPtc) if (context.HasPtc && !singleStep)
{ {
options |= CompilerOptions.Relocatable; options |= CompilerOptions.Relocatable;
} }
CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options); CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options);
if (context.HasPtc) if (context.HasPtc && !singleStep)
{ {
Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize); Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize);

View file

@ -112,12 +112,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
private ReadOnlySpan<float> GetFdnDelayTimesByLateMode(ReverbLateMode lateMode) private ReadOnlySpan<float> GetFdnDelayTimesByLateMode(ReverbLateMode lateMode)
{ {
return FdnDelayTimes.AsSpan().Slice((int)lateMode * 4, 4); return FdnDelayTimes.AsSpan((int)lateMode * 4, 4);
} }
private ReadOnlySpan<float> GetDecayDelayTimesByLateMode(ReverbLateMode lateMode) private ReadOnlySpan<float> GetDecayDelayTimesByLateMode(ReverbLateMode lateMode)
{ {
return DecayDelayTimes.AsSpan().Slice((int)lateMode * 4, 4); return DecayDelayTimes.AsSpan((int)lateMode * 4, 4);
} }
public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported) public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported)

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

@ -459,7 +459,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) for (int i = 0; i < Constants.VoiceWaveBufferCount; i++)
{ {
UpdateWaveBuffer(errorInfos.AsSpan().Slice(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext);
} }
} }

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

@ -47,6 +47,7 @@ namespace Ryujinx.Common.Logging
ServiceNim, ServiceNim,
ServiceNs, ServiceNs,
ServiceNsd, ServiceNsd,
ServiceNtc,
ServiceNv, ServiceNv,
ServiceOlsc, ServiceOlsc,
ServicePctl, ServicePctl,

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

@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.GAL
R32G32B32A32Sint, R32G32B32A32Sint,
S8Uint, S8Uint,
D16Unorm, D16Unorm,
D24X8Unorm, S8UintD24Unorm,
D32Float, D32Float,
D24UnormS8Uint, D24UnormS8Uint,
D32FloatS8Uint, D32FloatS8Uint,
@ -266,7 +266,7 @@ namespace Ryujinx.Graphics.GAL
{ {
case Format.D16Unorm: case Format.D16Unorm:
case Format.D24UnormS8Uint: case Format.D24UnormS8Uint:
case Format.D24X8Unorm: case Format.S8UintD24Unorm:
case Format.D32Float: case Format.D32Float:
case Format.D32FloatS8Uint: case Format.D32FloatS8Uint:
case Format.S8Uint: case Format.S8Uint:

View file

@ -1,11 +1,15 @@
using Ryujinx.Graphics.Device; using Ryujinx.Common;
using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using Ryujinx.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Gpu.Engine.Twod namespace Ryujinx.Graphics.Gpu.Engine.Twod
{ {
@ -44,6 +48,180 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
/// <param name="data">Data to be written</param> /// <param name="data">Data to be written</param>
public void Write(int offset, int data) => _state.Write(offset, data); public void Write(int offset, int data) => _state.Write(offset, data);
/// <summary>
/// Determines if data is compatible between the source and destination texture.
/// The two textures must have the same size, layout, and bytes per pixel.
/// </summary>
/// <param name="lhs">Info for the first texture</param>
/// <param name="rhs">Info for the second texture</param>
/// <param name="lhsFormat">Format of the first texture</param>
/// <param name="rhsFormat">Format of the second texture</param>
/// <returns>True if the data is compatible, false otherwise</returns>
private bool IsDataCompatible(TwodTexture lhs, TwodTexture rhs, FormatInfo lhsFormat, FormatInfo rhsFormat)
{
if (lhsFormat.BytesPerPixel != rhsFormat.BytesPerPixel ||
lhs.Height != rhs.Height ||
lhs.Depth != rhs.Depth ||
lhs.LinearLayout != rhs.LinearLayout ||
lhs.MemoryLayout.Packed != rhs.MemoryLayout.Packed)
{
return false;
}
if (lhs.LinearLayout)
{
return lhs.Stride == rhs.Stride;
}
else
{
return lhs.Width == rhs.Width;
}
}
/// <summary>
/// Determine if the given region covers the full texture, also considering width alignment.
/// </summary>
/// <param name="texture">The texture to check</param>
/// <param name="formatInfo"></param>
/// <param name="x1">Region start x</param>
/// <param name="y1">Region start y</param>
/// <param name="x2">Region end x</param>
/// <param name="y2">Region end y</param>
/// <returns>True if the region covers the full texture, false otherwise</returns>
private bool IsCopyRegionComplete(TwodTexture texture, FormatInfo formatInfo, int x1, int y1, int x2, int y2)
{
if (x1 != 0 || y1 != 0 || y2 != texture.Height)
{
return false;
}
int width;
int widthAlignment;
if (texture.LinearLayout)
{
widthAlignment = 1;
width = texture.Stride / formatInfo.BytesPerPixel;
}
else
{
widthAlignment = Constants.GobAlignment / formatInfo.BytesPerPixel;
width = texture.Width;
}
return width == BitUtils.AlignUp(x2, widthAlignment);
}
/// <summary>
/// Performs a full data copy between two textures, reading and writing guest memory directly.
/// The textures must have a matching layout, size, and bytes per pixel.
/// </summary>
/// <param name="src">The source texture</param>
/// <param name="dst">The destination texture</param>
/// <param name="w">Copy width</param>
/// <param name="h">Copy height</param>
/// <param name="bpp">Bytes per pixel</param>
private void UnscaledFullCopy(TwodTexture src, TwodTexture dst, int w, int h, int bpp)
{
var srcCalculator = new OffsetCalculator(
w,
h,
src.Stride,
src.LinearLayout,
src.MemoryLayout.UnpackGobBlocksInY(),
src.MemoryLayout.UnpackGobBlocksInZ(),
bpp);
(int _, int srcSize) = srcCalculator.GetRectangleRange(0, 0, w, h);
var memoryManager = _channel.MemoryManager;
ulong srcGpuVa = src.Address.Pack();
ulong dstGpuVa = dst.Address.Pack();
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa, srcSize, true);
int width;
int height = src.Height;
if (src.LinearLayout)
{
width = src.Stride / bpp;
}
else
{
width = src.Width;
}
// If the copy is not equal to the width and height of the texture, we will need to copy partially.
// It's worth noting that it has already been established that the src and dst are the same size.
if (w == width && h == height)
{
memoryManager.Write(dstGpuVa, srcSpan);
}
else
{
using WritableRegion dstRegion = memoryManager.GetWritableRegion(dstGpuVa, srcSize, true);
Span<byte> dstSpan = dstRegion.Memory.Span;
if (src.LinearLayout)
{
int stride = src.Stride;
int offset = 0;
int lineSize = width * bpp;
for (int y = 0; y < height; y++)
{
srcSpan.Slice(offset, lineSize).CopyTo(dstSpan.Slice(offset));
offset += stride;
}
}
else
{
// Copy with the block linear layout in mind.
// Recreate the offset calculate with bpp 1 for copy.
int stride = w * bpp;
srcCalculator = new OffsetCalculator(
stride,
h,
0,
false,
src.MemoryLayout.UnpackGobBlocksInY(),
src.MemoryLayout.UnpackGobBlocksInZ(),
1);
int strideTrunc = BitUtils.AlignDown(stride, 16);
ReadOnlySpan<Vector128<byte>> srcVec = MemoryMarshal.Cast<byte, Vector128<byte>>(srcSpan);
Span<Vector128<byte>> dstVec = MemoryMarshal.Cast<byte, Vector128<byte>>(dstSpan);
for (int y = 0; y < h; y++)
{
int x = 0;
srcCalculator.SetY(y);
for (; x < strideTrunc; x += 16)
{
int offset = srcCalculator.GetOffset(x) >> 4;
dstVec[offset] = srcVec[offset];
}
for (; x < stride; x++)
{
int offset = srcCalculator.GetOffset(x);
dstSpan[offset] = srcSpan[offset];
}
}
}
}
}
/// <summary> /// <summary>
/// Performs the blit operation, triggered by the register write. /// Performs the blit operation, triggered by the register write.
/// </summary> /// </summary>
@ -114,16 +292,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
srcX1 = 0; srcX1 = 0;
} }
FormatInfo dstCopyTextureFormat = dstCopyTexture.Format.Convert();
bool canDirectCopy = GraphicsConfig.Fast2DCopy &&
srcX2 == dstX2 && srcY2 == dstY2 &&
IsDataCompatible(srcCopyTexture, dstCopyTexture, srcCopyTextureFormat, dstCopyTextureFormat) &&
IsCopyRegionComplete(srcCopyTexture, srcCopyTextureFormat, srcX1, srcY1, srcX2, srcY2) &&
IsCopyRegionComplete(dstCopyTexture, dstCopyTextureFormat, dstX1, dstY1, dstX2, dstY2);
var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture( var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
memoryManager, memoryManager,
srcCopyTexture, srcCopyTexture,
offset, offset,
srcCopyTextureFormat, srcCopyTextureFormat,
!canDirectCopy,
false, false,
srcHint); srcHint);
if (srcTexture == null) if (srcTexture == null)
{ {
if (canDirectCopy)
{
// Directly copy the data on CPU.
UnscaledFullCopy(srcCopyTexture, dstCopyTexture, srcX2, srcY2, srcCopyTextureFormat.BytesPerPixel);
}
return; return;
} }
@ -132,7 +325,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
// When the source texture that was found has a depth format, // When the source texture that was found has a depth format,
// we must enforce the target texture also has a depth format, // we must enforce the target texture also has a depth format,
// as copies between depth and color formats are not allowed. // as copies between depth and color formats are not allowed.
FormatInfo dstCopyTextureFormat;
if (srcTexture.Format.IsDepthOrStencil()) if (srcTexture.Format.IsDepthOrStencil())
{ {
@ -148,6 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
dstCopyTexture, dstCopyTexture,
0, 0,
dstCopyTextureFormat, dstCopyTextureFormat,
true,
srcTexture.ScaleMode == TextureScaleMode.Scaled, srcTexture.ScaleMode == TextureScaleMode.Scaled,
dstHint); dstHint);

View file

@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1), ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2), ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1), ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1),
ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2), ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1), ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2), ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
_ => FormatInfo.Default _ => FormatInfo.Default

View file

@ -28,6 +28,14 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
public static bool FastGpuTime = true; public static bool FastGpuTime = true;
/// <summary>
/// Enables or disables fast 2d engine texture copies entirely on CPU when possible.
/// Reduces stuttering and # of textures in games that copy textures around for streaming,
/// as textures will not need to be created for the copy, and the data does not need to be
/// flushed from GPU.
/// </summary>
public static bool Fast2DCopy = true;
/// <summary> /// <summary>
/// Enables or disables the Just-in-Time compiler for GPU Macro code. /// Enables or disables the Just-in-Time compiler for GPU Macro code.
/// </summary> /// </summary>

View file

@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ 0x24a0e, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, { 0x24a0e, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ 0x24a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, { 0x24a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ 0x48a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, { 0x48a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ 0x4912b, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) },
{ 0x25385, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, { 0x25385, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, { 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) }, { 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) },

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>
@ -194,6 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TwodTexture copyTexture, TwodTexture copyTexture,
ulong offset, ulong offset,
FormatInfo formatInfo, FormatInfo formatInfo,
bool shouldCreate,
bool preferScaling = true, bool preferScaling = true,
Size? sizeHint = null) Size? sizeHint = null)
{ {
@ -234,6 +245,11 @@ namespace Ryujinx.Graphics.Gpu.Image
flags |= TextureSearchFlags.WithUpscale; flags |= TextureSearchFlags.WithUpscale;
} }
if (!shouldCreate)
{
flags |= TextureSearchFlags.NoCreate;
}
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint); Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint);
texture?.SynchronizeMemory(); texture?.SynchronizeMemory();
@ -480,15 +496,29 @@ namespace Ryujinx.Graphics.Gpu.Image
return texture; return texture;
} }
else if (flags.HasFlag(TextureSearchFlags.NoCreate))
{
return null;
}
// Calculate texture sizes, used to find all overlapping textures. // Calculate texture sizes, used to find all overlapping textures.
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.
@ -774,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++)
@ -1069,6 +1107,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
_textures.Remove(texture); _textures.Remove(texture);
} }
lock (_partiallyMappedTextures)
{
_partiallyMappedTextures.Remove(texture);
}
} }
/// <summary> /// <summary>

View file

@ -203,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint || if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
lhs.FormatInfo.Format == Format.D24X8Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm) lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
{ {
return TextureMatchQuality.FormatAlias; return TextureMatchQuality.FormatAlias;
} }

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

@ -362,7 +362,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return DepthStencilMode.Depth; return DepthStencilMode.Depth;
} }
if (format == Format.D24X8Unorm || format == Format.D24UnormS8Uint) if (format == Format.D24UnormS8Uint)
{ {
return component == SwizzleComponent.Red return component == SwizzleComponent.Red
? DepthStencilMode.Stencil ? DepthStencilMode.Stencil

View file

@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Strict = 1 << 0, Strict = 1 << 0,
ForSampler = 1 << 1, ForSampler = 1 << 1,
ForCopy = 1 << 2, ForCopy = 1 << 2,
WithUpscale = 1 << 3 WithUpscale = 1 << 3,
NoCreate = 1 << 4
} }
} }

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;
@ -154,14 +154,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Gets a writable region from GPU mapped memory. /// Gets a writable region from GPU mapped memory.
/// </summary> /// </summary>
/// <param name="address">Start address of the range</param> /// <param name="va">Start address of the range</param>
/// <param name="size">Size in bytes to be range</param> /// <param name="size">Size in bytes to be range</param>
/// <param name="tracked">True if write tracking is triggered on the span</param>
/// <returns>A writable region with the data at the specified memory location</returns> /// <returns>A writable region with the data at the specified memory location</returns>
public WritableRegion GetWritableRegion(ulong va, int size) public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{ {
if (IsContiguous(va, size)) if (IsContiguous(va, size))
{ {
return Physical.GetWritableRegion(Translate(va), size); return Physical.GetWritableRegion(Translate(va), size, tracked);
} }
else else
{ {
@ -169,7 +170,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
GetSpan(va, size).CopyTo(memory.Span); GetSpan(va, size).CopyTo(memory.Span);
return new WritableRegion(this, va, memory); return new WritableRegion(this, va, memory, tracked);
} }
} }
@ -339,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))
@ -347,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));
@ -366,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;
@ -404,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;
@ -418,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

@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
dataSpan[i++] = hash; dataSpan[i++] = hash;
} }
manifestHeader.UpdateChecksum(data.AsSpan().Slice(Unsafe.SizeOf<CacheManifestHeader>())); manifestHeader.UpdateChecksum(data.AsSpan(Unsafe.SizeOf<CacheManifestHeader>()));
MemoryMarshal.Write(data, ref manifestHeader); MemoryMarshal.Write(data, ref manifestHeader);
@ -447,12 +447,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
if (cb1DataAddress != 0 && cb1DataSize != 0) if (cb1DataAddress != 0 && cb1DataSize != 0)
{ {
memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan().Slice(size, cb1DataSize)); memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan(size, cb1DataSize));
} }
if (translatorContext2 != null) if (translatorContext2 != null)
{ {
memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan().Slice(size + cb1DataSize, sizeA)); memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan(size + cb1DataSize, sizeA));
} }
GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor); GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor);

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;
@ -206,7 +206,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
program = new ShaderProgram(entry.Header.Stage, ""); program = new ShaderProgram(entry.Header.Stage, "");
shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo(); shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo();
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code); ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code);
@ -244,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
return true; // Exit early, the decoding step failed. return true; // Exit early, the decoding step failed.
} }
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code); ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, code);
@ -394,8 +394,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
// NOTE: Vertex B comes first in the shader cache. // NOTE: Vertex B comes first in the shader cache.
byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray() : null; byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2); shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2);

View file

@ -6,15 +6,15 @@ namespace Ryujinx.Graphics.OpenGL
{ {
struct FormatTable struct FormatTable
{ {
private static FormatInfo[] Table; private static FormatInfo[] _table;
private static SizedInternalFormat[] TableImage; private static SizedInternalFormat[] _tableImage;
static FormatTable() static FormatTable()
{ {
int tableSize = Enum.GetNames<Format>().Length; int tableSize = Enum.GetNames<Format>().Length;
Table = new FormatInfo[tableSize]; _table = new FormatInfo[tableSize];
TableImage = new SizedInternalFormat[tableSize]; _tableImage = new SizedInternalFormat[tableSize];
Add(Format.R8Unorm, new FormatInfo(1, true, false, All.R8, PixelFormat.Red, PixelType.UnsignedByte)); Add(Format.R8Unorm, new FormatInfo(1, true, false, All.R8, PixelFormat.Red, PixelType.UnsignedByte));
Add(Format.R8Snorm, new FormatInfo(1, true, false, All.R8Snorm, PixelFormat.Red, PixelType.Byte)); Add(Format.R8Snorm, new FormatInfo(1, true, false, All.R8Snorm, PixelFormat.Red, PixelType.Byte));
@ -66,7 +66,7 @@ namespace Ryujinx.Graphics.OpenGL
Add(Format.R32G32B32A32Sint, new FormatInfo(4, false, false, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int)); Add(Format.R32G32B32A32Sint, new FormatInfo(4, false, false, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int));
Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte)); Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte));
Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort)); Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort));
Add(Format.D24X8Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt)); Add(Format.S8UintD24Unorm, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float)); Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float));
Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248)); Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248));
Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev)); Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev));
@ -218,22 +218,22 @@ namespace Ryujinx.Graphics.OpenGL
private static void Add(Format format, FormatInfo info) private static void Add(Format format, FormatInfo info)
{ {
Table[(int)format] = info; _table[(int)format] = info;
} }
private static void Add(Format format, SizedInternalFormat sif) private static void Add(Format format, SizedInternalFormat sif)
{ {
TableImage[(int)format] = sif; _tableImage[(int)format] = sif;
} }
public static FormatInfo GetFormatInfo(Format format) public static FormatInfo GetFormatInfo(Format format)
{ {
return Table[(int)format]; return _table[(int)format];
} }
public static SizedInternalFormat GetImageFormat(Format format) public static SizedInternalFormat GetImageFormat(Format format)
{ {
return TableImage[(int)format]; return _tableImage[(int)format];
} }
} }
} }

View file

@ -127,14 +127,13 @@ namespace Ryujinx.Graphics.OpenGL
private static bool IsPackedDepthStencilFormat(Format format) private static bool IsPackedDepthStencilFormat(Format format)
{ {
return format == Format.D24UnormS8Uint || return format == Format.D24UnormS8Uint ||
format == Format.D32FloatS8Uint; format == Format.D32FloatS8Uint ||
format == Format.S8UintD24Unorm;
} }
private static bool IsDepthOnlyFormat(Format format) private static bool IsDepthOnlyFormat(Format format)
{ {
return format == Format.D16Unorm || return format == Format.D16Unorm || format == Format.D32Float;
format == Format.D24X8Unorm ||
format == Format.D32Float;
} }
public void Dispose() public void Dispose()

View file

@ -0,0 +1,149 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Ryujinx.Graphics.OpenGL.Image
{
static class FormatConverter
{
public unsafe static byte[] ConvertS8D24ToD24S8(ReadOnlySpan<byte> data)
{
byte[] output = new byte[data.Length];
int start = 0;
if (Avx2.IsSupported)
{
var mask = Vector256.Create(
(byte)3, (byte)0, (byte)1, (byte)2,
(byte)7, (byte)4, (byte)5, (byte)6,
(byte)11, (byte)8, (byte)9, (byte)10,
(byte)15, (byte)12, (byte)13, (byte)14,
(byte)19, (byte)16, (byte)17, (byte)18,
(byte)23, (byte)20, (byte)21, (byte)22,
(byte)27, (byte)24, (byte)25, (byte)26,
(byte)31, (byte)28, (byte)29, (byte)30);
int sizeAligned = data.Length & ~31;
fixed (byte* pInput = data, pOutput = output)
{
for (uint i = 0; i < sizeAligned; i += 32)
{
var dataVec = Avx.LoadVector256(pInput + i);
dataVec = Avx2.Shuffle(dataVec, mask);
Avx.Store(pOutput + i, dataVec);
}
}
start = sizeAligned;
}
else if (Ssse3.IsSupported)
{
var mask = Vector128.Create(
(byte)3, (byte)0, (byte)1, (byte)2,
(byte)7, (byte)4, (byte)5, (byte)6,
(byte)11, (byte)8, (byte)9, (byte)10,
(byte)15, (byte)12, (byte)13, (byte)14);
int sizeAligned = data.Length & ~15;
fixed (byte* pInput = data, pOutput = output)
{
for (uint i = 0; i < sizeAligned; i += 16)
{
var dataVec = Sse2.LoadVector128(pInput + i);
dataVec = Ssse3.Shuffle(dataVec, mask);
Sse2.Store(pOutput + i, dataVec);
}
}
start = sizeAligned;
}
var outSpan = MemoryMarshal.Cast<byte, uint>(output);
var dataSpan = MemoryMarshal.Cast<byte, uint>(data);
for (int i = start / sizeof(uint); i < dataSpan.Length; i++)
{
outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8);
}
return output;
}
public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan<byte> data)
{
byte[] output = new byte[data.Length];
int start = 0;
if (Avx2.IsSupported)
{
var mask = Vector256.Create(
(byte)1, (byte)2, (byte)3, (byte)0,
(byte)5, (byte)6, (byte)7, (byte)4,
(byte)9, (byte)10, (byte)11, (byte)8,
(byte)13, (byte)14, (byte)15, (byte)12,
(byte)17, (byte)18, (byte)19, (byte)16,
(byte)21, (byte)22, (byte)23, (byte)20,
(byte)25, (byte)26, (byte)27, (byte)24,
(byte)29, (byte)30, (byte)31, (byte)28);
int sizeAligned = data.Length & ~31;
fixed (byte* pInput = data, pOutput = output)
{
for (uint i = 0; i < sizeAligned; i += 32)
{
var dataVec = Avx.LoadVector256(pInput + i);
dataVec = Avx2.Shuffle(dataVec, mask);
Avx.Store(pOutput + i, dataVec);
}
}
start = sizeAligned;
}
else if (Ssse3.IsSupported)
{
var mask = Vector128.Create(
(byte)1, (byte)2, (byte)3, (byte)0,
(byte)5, (byte)6, (byte)7, (byte)4,
(byte)9, (byte)10, (byte)11, (byte)8,
(byte)13, (byte)14, (byte)15, (byte)12);
int sizeAligned = data.Length & ~15;
fixed (byte* pInput = data, pOutput = output)
{
for (uint i = 0; i < sizeAligned; i += 16)
{
var dataVec = Sse2.LoadVector128(pInput + i);
dataVec = Ssse3.Shuffle(dataVec, mask);
Sse2.Store(pOutput + i, dataVec);
}
}
start = sizeAligned;
}
var outSpan = MemoryMarshal.Cast<byte, uint>(output);
var dataSpan = MemoryMarshal.Cast<byte, uint>(data);
for (int i = start / sizeof(uint); i < dataSpan.Length; i++)
{
outSpan[i] = BitOperations.RotateRight(dataSpan[i], 8);
}
return output;
}
}
}

View file

@ -291,7 +291,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
private static ClearBufferMask GetMask(Format format) private static ClearBufferMask GetMask(Format format)
{ {
if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint) if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint || format == Format.S8UintD24Unorm)
{ {
return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit; return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit;
} }
@ -311,9 +311,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
private static bool IsDepthOnly(Format format) private static bool IsDepthOnly(Format format)
{ {
return format == Format.D16Unorm || return format == Format.D16Unorm || format == Format.D32Float;
format == Format.D24X8Unorm ||
format == Format.D32Float;
} }
public TextureView BgraSwap(TextureView from) public TextureView BgraSwap(TextureView from)

View file

@ -140,9 +140,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
size += Info.GetMipSize(level); size += Info.GetMipSize(level);
} }
ReadOnlySpan<byte> data;
if (HwCapabilities.UsePersistentBufferForFlush) if (HwCapabilities.UsePersistentBufferForFlush)
{ {
return _renderer.PersistentBuffers.Default.GetTextureData(this, size); data = _renderer.PersistentBuffers.Default.GetTextureData(this, size);
} }
else else
{ {
@ -150,8 +152,15 @@ namespace Ryujinx.Graphics.OpenGL.Image
WriteTo(target); WriteTo(target);
return new ReadOnlySpan<byte>(target.ToPointer(), size); data = new ReadOnlySpan<byte>(target.ToPointer(), size);
} }
if (Format == Format.S8UintD24Unorm)
{
data = FormatConverter.ConvertD24S8ToS8D24(data);
}
return data;
} }
public unsafe ReadOnlySpan<byte> GetData(int layer, int level) public unsafe ReadOnlySpan<byte> GetData(int layer, int level)
@ -285,6 +294,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
public void SetData(ReadOnlySpan<byte> data) public void SetData(ReadOnlySpan<byte> data)
{ {
if (Format == Format.S8UintD24Unorm)
{
data = FormatConverter.ConvertS8D24ToD24S8(data);
}
unsafe unsafe
{ {
fixed (byte* ptr = data) fixed (byte* ptr = data)
@ -296,6 +310,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
public void SetData(ReadOnlySpan<byte> data, int layer, int level) public void SetData(ReadOnlySpan<byte> data, int layer, int level)
{ {
if (Format == Format.S8UintD24Unorm)
{
data = FormatConverter.ConvertS8D24ToD24S8(data);
}
unsafe unsafe
{ {
fixed (byte* ptr = data) fixed (byte* ptr = data)

View file

@ -124,7 +124,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.GetProgramBinary(Handle, size, out _, out BinaryFormat binFormat, data); GL.GetProgramBinary(Handle, size, out _, out BinaryFormat binFormat, data);
BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan().Slice(size, 4), (int)binFormat); BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan(size, 4), (int)binFormat);
return data; return data;
} }

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

@ -14,6 +14,9 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
{ {
private GeneralServiceDetail _generalServiceDetail; private GeneralServiceDetail _generalServiceDetail;
private IPInterfaceProperties _targetPropertiesCache = null;
private UnicastIPAddressInformation _targetAddressInfoCache = null;
public IGeneralService() public IGeneralService()
{ {
_generalServiceDetail = new GeneralServiceDetail _generalServiceDetail = new GeneralServiceDetail
@ -22,6 +25,8 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
IsAnyInternetRequestAccepted = true // NOTE: Why not accept any internet request? IsAnyInternetRequestAccepted = true // NOTE: Why not accept any internet request?
}; };
NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(LocalInterfaceCacheHandler);
GeneralServiceManager.Add(_generalServiceDetail); GeneralServiceManager.Add(_generalServiceDetail);
} }
@ -165,6 +170,11 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
return (null, null); return (null, null);
} }
if (_targetPropertiesCache != null && _targetAddressInfoCache != null)
{
return (_targetPropertiesCache, _targetAddressInfoCache);
}
IPInterfaceProperties targetProperties = null; IPInterfaceProperties targetProperties = null;
UnicastIPAddressInformation targetAddressInfo = null; UnicastIPAddressInformation targetAddressInfo = null;
@ -194,13 +204,26 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
} }
} }
_targetPropertiesCache = targetProperties;
_targetAddressInfoCache = targetAddressInfo;
return (targetProperties, targetAddressInfo); return (targetProperties, targetAddressInfo);
} }
private void LocalInterfaceCacheHandler(object sender, EventArgs e)
{
Logger.Info?.Print(LogClass.ServiceNifm, $"NetworkAddress changed, invalidating cached data.");
_targetPropertiesCache = null;
_targetAddressInfoCache = null;
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
if (isDisposing) if (isDisposing)
{ {
NetworkChange.NetworkAddressChanged -= LocalInterfaceCacheHandler;
GeneralServiceManager.Remove(_generalServiceDetail.ClientId); GeneralServiceManager.Remove(_generalServiceDetail.ClientId);
} }
} }

View file

@ -1,8 +1,24 @@
namespace Ryujinx.HLE.HOS.Services.Nim.Ntc using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService;
namespace Ryujinx.HLE.HOS.Services.Nim.Ntc
{ {
[Service("ntc")] [Service("ntc")]
class IStaticService : IpcService class IStaticService : IpcService
{ {
public IStaticService(ServiceCtx context) { } public IStaticService(ServiceCtx context) { }
[CommandHipc(0)]
// OpenEnsureNetworkClockAvailabilityService(u64) -> object<nn::ntc::detail::service::IEnsureNetworkClockAvailabilityService>
public ResultCode CreateAsyncInterface(ServiceCtx context)
{
ulong unknown = context.RequestData.ReadUInt64();
MakeObject(context, new IEnsureNetworkClockAvailabilityService(context));
Logger.Stub?.PrintStub(LogClass.ServiceNtc, new { unknown });
return ResultCode.Success;
}
} }
} }

View file

@ -0,0 +1,77 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService
{
class IEnsureNetworkClockAvailabilityService : IpcService
{
private KEvent _finishNotificationEvent;
private ResultCode _taskResultCode;
public IEnsureNetworkClockAvailabilityService(ServiceCtx context)
{
_finishNotificationEvent = new KEvent(context.Device.System.KernelContext);
_taskResultCode = ResultCode.Success;
// NOTE: The service starts a thread that polls Nintendo NTP server and syncs the time with it.
// Additionnally it gets and uses some settings too:
// autonomic_correction_interval_seconds, autonomic_correction_failed_retry_interval_seconds,
// autonomic_correction_immediate_try_count_max, autonomic_correction_immediate_try_interval_milliseconds
}
[CommandHipc(0)]
// StartTask()
public ResultCode StartTask(ServiceCtx context)
{
if (!context.Device.Configuration.EnableInternetAccess)
{
return (ResultCode)Time.ResultCode.NetworkTimeNotAvailable;
}
// NOTE: Since we don't support the Nintendo NTP server, we can signal the event now to confirm the update task is done.
_finishNotificationEvent.ReadableEvent.Signal();
Logger.Stub?.PrintStub(LogClass.ServiceNtc);
return ResultCode.Success;
}
[CommandHipc(1)]
// GetFinishNotificationEvent() -> handle<copy>
public ResultCode GetFinishNotificationEvent(ServiceCtx context)
{
if (context.Process.HandleTable.GenerateHandle(_finishNotificationEvent.ReadableEvent, out int finishNotificationEventHandle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(finishNotificationEventHandle);
return ResultCode.Success;
}
[CommandHipc(2)]
// GetResult()
public ResultCode GetResult(ServiceCtx context)
{
return _taskResultCode;
}
[CommandHipc(3)]
// Cancel()
public ResultCode Cancel(ServiceCtx context)
{
// NOTE: The update task should be canceled here.
_finishNotificationEvent.ReadableEvent.Signal();
_taskResultCode = (ResultCode)Time.ResultCode.NetworkTimeTaskCanceled;
Logger.Stub?.PrintStub(LogClass.ServiceNtc);
return ResultCode.Success;
}
}
}

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

@ -185,7 +185,7 @@ namespace Ryujinx.HLE.HOS.Services
{ {
for (int i = 0; i < request.RecvListBuff.Count; i++) for (int i = 0; i < request.RecvListBuff.Count; i++)
{ {
ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan().Slice(sizesOffset + i * 2, 2)); ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2));
response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size)); response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size));

View file

@ -18,5 +18,7 @@
TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId, TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId,
TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId, TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId,
NotImplemented = (990 << ErrorCodeShift) | ModuleId, NotImplemented = (990 << ErrorCodeShift) | ModuleId,
NetworkTimeNotAvailable = (1000 << ErrorCodeShift) | ModuleId,
NetworkTimeTaskCanceled = (1003 << ErrorCodeShift) | ModuleId,
} }
} }

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

@ -8,9 +8,9 @@ namespace Ryujinx.HLE.Loaders.Executables
class KipExecutable : IExecutable class KipExecutable : IExecutable
{ {
public byte[] Program { get; } public byte[] Program { get; }
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize); public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize); public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize); public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
public uint TextOffset { get; } public uint TextOffset { get; }
public uint RoOffset { get; } public uint RoOffset { get; }
@ -76,7 +76,7 @@ namespace Ryujinx.HLE.Loaders.Executables
{ {
reader.GetSegmentSize(segmentType, out int uncompressedSize).ThrowIfFailure(); reader.GetSegmentSize(segmentType, out int uncompressedSize).ThrowIfFailure();
var span = program.AsSpan().Slice((int)offset, uncompressedSize); var span = program.AsSpan((int)offset, uncompressedSize);
reader.ReadSegment(segmentType, span).ThrowIfFailure(); reader.ReadSegment(segmentType, span).ThrowIfFailure();

View file

@ -7,9 +7,9 @@ namespace Ryujinx.HLE.Loaders.Executables
class NroExecutable : Nro, IExecutable class NroExecutable : Nro, IExecutable
{ {
public byte[] Program { get; } public byte[] Program { get; }
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)Header.NroSegments[0].Size); public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)Header.NroSegments[0].Size);
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)Header.NroSegments[1].Size); public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)Header.NroSegments[1].Size);
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)Header.NroSegments[2].Size); public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)Header.NroSegments[2].Size);
public uint TextOffset => Header.NroSegments[0].FileOffset; public uint TextOffset => Header.NroSegments[0].FileOffset;
public uint RoOffset => Header.NroSegments[1].FileOffset; public uint RoOffset => Header.NroSegments[1].FileOffset;

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;
@ -12,9 +12,9 @@ namespace Ryujinx.HLE.Loaders.Executables
class NsoExecutable : IExecutable class NsoExecutable : IExecutable
{ {
public byte[] Program { get; } public byte[] Program { get; }
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize); public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize); public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize); public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
public uint TextOffset { get; } public uint TextOffset { get; }
public uint RoOffset { get; } public uint RoOffset { get; }
@ -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)
{ {
@ -58,7 +58,7 @@ namespace Ryujinx.HLE.Loaders.Executables
{ {
reader.GetSegmentSize(segmentType, out uint uncompressedSize).ThrowIfFailure(); reader.GetSegmentSize(segmentType, out uint uncompressedSize).ThrowIfFailure();
var span = Program.AsSpan().Slice((int)offset, (int)uncompressedSize); var span = Program.AsSpan((int)offset, (int)uncompressedSize);
reader.ReadSegment(segmentType, span).ThrowIfFailure(); reader.ReadSegment(segmentType, span).ThrowIfFailure();

View file

@ -85,7 +85,7 @@ namespace Ryujinx.HLE.Loaders.Mods
Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}"); Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}");
patch.AsSpan().Slice(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize)); patch.AsSpan(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize));
count++; count++;
} }

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

Some files were not shown because too many files have changed in this diff Show more