Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
79becc4b78 | |||
223172ac0b | |||
8c9633d72f | |||
1f93fd52d9 | |||
aac7bbd378 | |||
bed516bfda | |||
69b05f9918 | |||
fb7c80e928 | |||
bb2f9df0a1 | |||
54bfaa125d | |||
7af9fcbc06 | |||
ee174be57c | |||
0bcbe32367 | |||
b97ff4da5e | |||
747081d2c7 | |||
497199bb50 | |||
bd9ac0fdaa | |||
ac21abbb9d | |||
a3dd04deef | |||
3705c20668 | |||
7b35ebc64a | |||
0a24aa6af2 | |||
c9c65af59e | |||
dc063eac83 | |||
ccf23fc629 | |||
f1460d5494 | |||
644b497df1 | |||
fb935fd201 | |||
f2087ca29e | |||
92d166ecb7 | |||
72e543e946 | |||
98c838b24c | |||
63c9c64196 | |||
a113ed0811 | |||
747876dc67 | |||
95cc18a7b4 |
@ -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;
|
||||||
}
|
}
|
||||||
|
9
ARMeilleure/Decoders/DecoderMode.cs
Normal file
9
ARMeilleure/Decoders/DecoderMode.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace ARMeilleure.Decoders
|
||||||
|
{
|
||||||
|
enum DecoderMode
|
||||||
|
{
|
||||||
|
MultipleBlocks,
|
||||||
|
SingleBlock,
|
||||||
|
SingleInstruction,
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,8 @@
|
|||||||
namespace ARMeilleure.Decoders
|
namespace ARMeilleure.Decoders
|
||||||
{
|
{
|
||||||
interface IOpCode32Alu : IOpCode32
|
interface IOpCode32Alu : IOpCode32, IOpCode32HasSetFlags
|
||||||
{
|
{
|
||||||
int Rd { get; }
|
int Rd { get; }
|
||||||
int Rn { get; }
|
int Rn { get; }
|
||||||
|
|
||||||
bool? SetFlags { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
7
ARMeilleure/Decoders/IOpCode32HasSetFlags.cs
Normal file
7
ARMeilleure/Decoders/IOpCode32HasSetFlags.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace ARMeilleure.Decoders
|
||||||
|
{
|
||||||
|
interface IOpCode32HasSetFlags
|
||||||
|
{
|
||||||
|
bool? SetFlags { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace ARMeilleure.Decoders
|
namespace ARMeilleure.Decoders
|
||||||
{
|
{
|
||||||
class OpCode32AluUmull : OpCode32
|
class OpCode32AluUmull : OpCode32, IOpCode32HasSetFlags
|
||||||
{
|
{
|
||||||
public int RdLo { get; }
|
public int RdLo { get; }
|
||||||
public int RdHi { get; }
|
public int RdHi { get; }
|
||||||
|
@ -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; }
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
|
|
||||||
|
14
ARMeilleure/Decoders/OpCodeT32.cs
Normal file
14
ARMeilleure/Decoders/OpCodeT32.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
ARMeilleure/Decoders/OpCodeT32Alu.cs
Normal file
20
ARMeilleure/Decoders/OpCodeT32Alu.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
ARMeilleure/Decoders/OpCodeT32AluImm.cs
Normal file
38
ARMeilleure/Decoders/OpCodeT32AluImm.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
ARMeilleure/Decoders/OpCodeT32AluRsImm.cs
Normal file
20
ARMeilleure/Decoders/OpCodeT32AluRsImm.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
ARMeilleure/Decoders/OpCodeT32BImm20.cs
Normal file
29
ARMeilleure/Decoders/OpCodeT32BImm20.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
ARMeilleure/Decoders/OpCodeT32BImm24.cs
Normal file
35
ARMeilleure/Decoders/OpCodeT32BImm24.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -14,7 +14,7 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
public static bool ShouldSetFlags(ArmEmitterContext context)
|
public static bool ShouldSetFlags(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
IOpCode32HasSetFlags op = (IOpCode32HasSetFlags)context.CurrOp;
|
||||||
|
|
||||||
if (op.SetFlags == null)
|
if (op.SetFlags == null)
|
||||||
{
|
{
|
||||||
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
static partial class InstEmit
|
static partial class InstEmit
|
||||||
{
|
{
|
||||||
private const int DczSizeLog2 = 4;
|
private const int DczSizeLog2 = 4; // Log2 size in words
|
||||||
|
public const int DczSizeInBytes = 4 << DczSizeLog2;
|
||||||
|
|
||||||
public static void Hint(ArmEmitterContext context)
|
public static void Hint(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
@ -87,7 +88,7 @@ namespace ARMeilleure.Instructions
|
|||||||
// DC ZVA
|
// DC ZVA
|
||||||
Operand t = GetIntOrZR(context, op.Rt);
|
Operand t = GetIntOrZR(context, op.Rt);
|
||||||
|
|
||||||
for (long offset = 0; offset < (4 << DczSizeLog2); offset += 8)
|
for (long offset = 0; offset < DczSizeInBytes; offset += 8)
|
||||||
{
|
{
|
||||||
Operand address = context.Add(t, Const(offset));
|
Operand address = context.Add(t, Const(offset));
|
||||||
|
|
||||||
@ -98,7 +99,12 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No-op
|
// No-op
|
||||||
case 0b11_011_0111_1110_001: //DC CIVAC
|
case 0b11_011_0111_1110_001: // DC CIVAC
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0b11_011_0111_0101_001: // IC IVAU
|
||||||
|
Operand target = Register(op.Rt, RegisterType.Integer, OperandType.I64);
|
||||||
|
context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)), target);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,11 @@ namespace ARMeilleure.Instructions
|
|||||||
return (ulong)function.FuncPtr.ToInt64();
|
return (ulong)function.FuncPtr.ToInt64();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void InvalidateCacheLine(ulong address)
|
||||||
|
{
|
||||||
|
Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool CheckSynchronization()
|
public static bool CheckSynchronization()
|
||||||
{
|
{
|
||||||
Statistics.PauseTimer();
|
Statistics.PauseTimer();
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -114,6 +114,7 @@ namespace ARMeilleure.Translation
|
|||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpscr))); // A32 only.
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpscr))); // A32 only.
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
|
||||||
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr)));
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
|
||||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
|
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
|
||||||
|
756
ARMeilleure/Translation/IntervalTree.cs
Normal file
756
ARMeilleure/Translation/IntervalTree.cs
Normal file
@ -0,0 +1,756 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ARMeilleure.Translation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="K">Key</typeparam>
|
||||||
|
/// <typeparam name="V">Value</typeparam>
|
||||||
|
public class IntervalTree<K, V> where K : IComparable<K>
|
||||||
|
{
|
||||||
|
private const int ArrayGrowthSize = 32;
|
||||||
|
|
||||||
|
private const bool Black = true;
|
||||||
|
private const bool Red = false;
|
||||||
|
private IntervalTreeNode<K, V> _root = null;
|
||||||
|
private int _count = 0;
|
||||||
|
|
||||||
|
public int Count => _count;
|
||||||
|
|
||||||
|
public IntervalTree() { }
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the values of the interval whose key is <paramref name="key"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Key of the node value to get</param>
|
||||||
|
/// <param name="value">Value with the given <paramref name="key"/></param>
|
||||||
|
/// <returns>True if the key is on the dictionary, false otherwise</returns>
|
||||||
|
public bool TryGet(K key, out V value)
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> node = GetNode(key);
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = node.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the start addresses of the intervals whose start and end keys overlap the given range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">Start of the range</param>
|
||||||
|
/// <param name="end">End of the range</param>
|
||||||
|
/// <param name="overlaps">Overlaps array to place results in</param>
|
||||||
|
/// <param name="overlapCount">Index to start writing results into the array. Defaults to 0</param>
|
||||||
|
/// <returns>Number of intervals found</returns>
|
||||||
|
public int Get(K start, K end, ref K[] overlaps, int overlapCount = 0)
|
||||||
|
{
|
||||||
|
GetValues(_root, start, end, ref overlaps, ref overlapCount);
|
||||||
|
|
||||||
|
return overlapCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new interval into the tree whose start is <paramref name="start"/>, end is <paramref name="end"/> and value is <paramref name="value"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">Start of the range to add</param>
|
||||||
|
/// <param name="end">End of the range to insert</param>
|
||||||
|
/// <param name="value">Value to add</param>
|
||||||
|
/// <param name="updateFactoryCallback">Optional factory used to create a new value if <paramref name="start"/> is already on the tree</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
|
||||||
|
/// <returns>True if the value was added, false if the start key was already in the dictionary</returns>
|
||||||
|
public bool AddOrUpdate(K start, K end, V value, Func<K, V, V> updateFactoryCallback)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return BSTInsert(start, end, value, updateFactoryCallback, out IntervalTreeNode<K, V> node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an existing or adds a new interval into the tree whose start is <paramref name="start"/>, end is <paramref name="end"/> and value is <paramref name="value"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">Start of the range to add</param>
|
||||||
|
/// <param name="end">End of the range to insert</param>
|
||||||
|
/// <param name="value">Value to add</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
|
||||||
|
/// <returns><paramref name="value"/> if <paramref name="start"/> is not yet on the tree, or the existing value otherwise</returns>
|
||||||
|
public V GetOrAdd(K start, K end, V value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
BSTInsert(start, end, value, null, out IntervalTreeNode<K, V> node);
|
||||||
|
return node.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a value from the tree, searching for it with <paramref name="key"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Key of the node to remove</param>
|
||||||
|
/// <returns>Number of deleted values</returns>
|
||||||
|
public int Remove(K key)
|
||||||
|
{
|
||||||
|
int removed = Delete(key);
|
||||||
|
|
||||||
|
_count -= removed;
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds all the nodes in the dictionary into <paramref name="list"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of all values sorted by Key Order</returns>
|
||||||
|
public List<V> AsList()
|
||||||
|
{
|
||||||
|
List<V> list = new List<V>();
|
||||||
|
|
||||||
|
AddToList(_root, list);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods (BST)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds all values that are children of or contained within <paramref name="node"/> into <paramref name="list"/>, in Key Order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">The node to search for values within</param>
|
||||||
|
/// <param name="list">The list to add values to</param>
|
||||||
|
private void AddToList(IntervalTreeNode<K, V> node, List<V> list)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddToList(node.Left, list);
|
||||||
|
|
||||||
|
list.Add(node.Value);
|
||||||
|
|
||||||
|
AddToList(node.Right, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the node reference whose key is <paramref name="key"/>, or null if no such node exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Key of the node to get</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||||
|
/// <returns>Node reference in the tree</returns>
|
||||||
|
private IntervalTreeNode<K, V> GetNode(K key)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
IntervalTreeNode<K, V> node = _root;
|
||||||
|
while (node != null)
|
||||||
|
{
|
||||||
|
int cmp = key.CompareTo(node.Start);
|
||||||
|
if (cmp < 0)
|
||||||
|
{
|
||||||
|
node = node.Left;
|
||||||
|
}
|
||||||
|
else if (cmp > 0)
|
||||||
|
{
|
||||||
|
node = node.Right;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve all values that overlap the given start and end keys.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">Start of the range</param>
|
||||||
|
/// <param name="end">End of the range</param>
|
||||||
|
/// <param name="overlaps">Overlaps array to place results in</param>
|
||||||
|
/// <param name="overlapCount">Overlaps count to update</param>
|
||||||
|
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref K[] overlaps, ref int overlapCount)
|
||||||
|
{
|
||||||
|
if (node == null || start.CompareTo(node.Max) >= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetValues(node.Left, start, end, ref overlaps, ref overlapCount);
|
||||||
|
|
||||||
|
bool endsOnRight = end.CompareTo(node.Start) > 0;
|
||||||
|
if (endsOnRight)
|
||||||
|
{
|
||||||
|
if (start.CompareTo(node.End) < 0)
|
||||||
|
{
|
||||||
|
if (overlaps.Length >= overlapCount)
|
||||||
|
{
|
||||||
|
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
overlaps[overlapCount++] = node.Start;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetValues(node.Right, start, end, ref overlaps, ref overlapCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Propagate an increase in max value starting at the given node, heading up the tree.
|
||||||
|
/// This should only be called if the max increases - not for rebalancing or removals.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">The node to start propagating from</param>
|
||||||
|
private void PropagateIncrease(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
K max = node.Max;
|
||||||
|
IntervalTreeNode<K, V> ptr = node;
|
||||||
|
|
||||||
|
while ((ptr = ptr.Parent) != null)
|
||||||
|
{
|
||||||
|
if (max.CompareTo(ptr.Max) > 0)
|
||||||
|
{
|
||||||
|
ptr.Max = max;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Propagate recalculating max value starting at the given node, heading up the tree.
|
||||||
|
/// This fully recalculates the max value from all children when there is potential for it to decrease.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">The node to start propagating from</param>
|
||||||
|
private void PropagateFull(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> ptr = node;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
K max = ptr.End;
|
||||||
|
|
||||||
|
if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0)
|
||||||
|
{
|
||||||
|
max = ptr.Left.Max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0)
|
||||||
|
{
|
||||||
|
max = ptr.Right.Max;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr.Max = max;
|
||||||
|
} while ((ptr = ptr.Parent) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key.
|
||||||
|
/// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than <paramref name="start"/>, and all children in the right subtree are greater than <paramref name="start"/>.
|
||||||
|
/// Each node can contain multiple values, and has an end address which is the maximum of all those values.
|
||||||
|
/// Post insertion, the "max" value of the node and all parents are updated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">Start of the range to insert</param>
|
||||||
|
/// <param name="end">End of the range to insert</param>
|
||||||
|
/// <param name="value">Value to insert</param>
|
||||||
|
/// <param name="updateFactoryCallback">Optional factory used to create a new value if <paramref name="start"/> is already on the tree</param>
|
||||||
|
/// <param name="outNode">Node that was inserted or modified</param>
|
||||||
|
/// <returns>True if <paramref name="start"/> was not yet on the tree, false otherwise</returns>
|
||||||
|
private bool BSTInsert(K start, K end, V value, Func<K, V, V> updateFactoryCallback, out IntervalTreeNode<K, V> outNode)
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> parent = null;
|
||||||
|
IntervalTreeNode<K, V> node = _root;
|
||||||
|
|
||||||
|
while (node != null)
|
||||||
|
{
|
||||||
|
parent = node;
|
||||||
|
int cmp = start.CompareTo(node.Start);
|
||||||
|
if (cmp < 0)
|
||||||
|
{
|
||||||
|
node = node.Left;
|
||||||
|
}
|
||||||
|
else if (cmp > 0)
|
||||||
|
{
|
||||||
|
node = node.Right;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outNode = node;
|
||||||
|
|
||||||
|
if (updateFactoryCallback != null)
|
||||||
|
{
|
||||||
|
// Replace
|
||||||
|
node.Value = updateFactoryCallback(start, node.Value);
|
||||||
|
|
||||||
|
int endCmp = end.CompareTo(node.End);
|
||||||
|
|
||||||
|
if (endCmp > 0)
|
||||||
|
{
|
||||||
|
node.End = end;
|
||||||
|
if (end.CompareTo(node.Max) > 0)
|
||||||
|
{
|
||||||
|
node.Max = end;
|
||||||
|
PropagateIncrease(node);
|
||||||
|
RestoreBalanceAfterInsertion(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (endCmp < 0)
|
||||||
|
{
|
||||||
|
node.End = end;
|
||||||
|
PropagateFull(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IntervalTreeNode<K, V> newNode = new IntervalTreeNode<K, V>(start, end, value, parent);
|
||||||
|
if (newNode.Parent == null)
|
||||||
|
{
|
||||||
|
_root = newNode;
|
||||||
|
}
|
||||||
|
else if (start.CompareTo(parent.Start) < 0)
|
||||||
|
{
|
||||||
|
parent.Left = newNode;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parent.Right = newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropagateIncrease(newNode);
|
||||||
|
_count++;
|
||||||
|
RestoreBalanceAfterInsertion(newNode);
|
||||||
|
outNode = newNode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the value from the dictionary after searching for it with <paramref name="key">.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Key to search for</param>
|
||||||
|
/// <returns>Number of deleted values</returns>
|
||||||
|
private int Delete(K key)
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> nodeToDelete = GetNode(key);
|
||||||
|
|
||||||
|
if (nodeToDelete == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntervalTreeNode<K, V> replacementNode;
|
||||||
|
|
||||||
|
if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null)
|
||||||
|
{
|
||||||
|
replacementNode = nodeToDelete;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
replacementNode = PredecessorOf(nodeToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
IntervalTreeNode<K, V> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
|
||||||
|
|
||||||
|
if (tmp != null)
|
||||||
|
{
|
||||||
|
tmp.Parent = ParentOf(replacementNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ParentOf(replacementNode) == null)
|
||||||
|
{
|
||||||
|
_root = tmp;
|
||||||
|
}
|
||||||
|
else if (replacementNode == LeftOf(ParentOf(replacementNode)))
|
||||||
|
{
|
||||||
|
ParentOf(replacementNode).Left = tmp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ParentOf(replacementNode).Right = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replacementNode != nodeToDelete)
|
||||||
|
{
|
||||||
|
nodeToDelete.Start = replacementNode.Start;
|
||||||
|
nodeToDelete.Value = replacementNode.Value;
|
||||||
|
nodeToDelete.End = replacementNode.End;
|
||||||
|
nodeToDelete.Max = replacementNode.Max;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropagateFull(replacementNode);
|
||||||
|
|
||||||
|
if (tmp != null && ColorOf(replacementNode) == Black)
|
||||||
|
{
|
||||||
|
RestoreBalanceAfterRemoval(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the node with the largest key where <paramref name="node"/> is considered the root node.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Root Node</param>
|
||||||
|
/// <returns>Node with the maximum key in the tree of <paramref name="node"/></returns>
|
||||||
|
private static IntervalTreeNode<K, V> Maximum(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> tmp = node;
|
||||||
|
while (tmp.Right != null)
|
||||||
|
{
|
||||||
|
tmp = tmp.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the node whose key is immediately less than <paramref name="node"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to find the predecessor of</param>
|
||||||
|
/// <returns>Predecessor of <paramref name="node"/></returns>
|
||||||
|
private static IntervalTreeNode<K, V> PredecessorOf(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
if (node.Left != null)
|
||||||
|
{
|
||||||
|
return Maximum(node.Left);
|
||||||
|
}
|
||||||
|
IntervalTreeNode<K, V> parent = node.Parent;
|
||||||
|
while (parent != null && node == parent.Left)
|
||||||
|
{
|
||||||
|
node = parent;
|
||||||
|
parent = parent.Parent;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods (RBL)
|
||||||
|
|
||||||
|
private void RestoreBalanceAfterRemoval(IntervalTreeNode<K, V> balanceNode)
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> ptr = balanceNode;
|
||||||
|
|
||||||
|
while (ptr != _root && ColorOf(ptr) == Black)
|
||||||
|
{
|
||||||
|
if (ptr == LeftOf(ParentOf(ptr)))
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> sibling = RightOf(ParentOf(ptr));
|
||||||
|
|
||||||
|
if (ColorOf(sibling) == Red)
|
||||||
|
{
|
||||||
|
SetColor(sibling, Black);
|
||||||
|
SetColor(ParentOf(ptr), Red);
|
||||||
|
RotateLeft(ParentOf(ptr));
|
||||||
|
sibling = RightOf(ParentOf(ptr));
|
||||||
|
}
|
||||||
|
if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black)
|
||||||
|
{
|
||||||
|
SetColor(sibling, Red);
|
||||||
|
ptr = ParentOf(ptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ColorOf(RightOf(sibling)) == Black)
|
||||||
|
{
|
||||||
|
SetColor(LeftOf(sibling), Black);
|
||||||
|
SetColor(sibling, Red);
|
||||||
|
RotateRight(sibling);
|
||||||
|
sibling = RightOf(ParentOf(ptr));
|
||||||
|
}
|
||||||
|
SetColor(sibling, ColorOf(ParentOf(ptr)));
|
||||||
|
SetColor(ParentOf(ptr), Black);
|
||||||
|
SetColor(RightOf(sibling), Black);
|
||||||
|
RotateLeft(ParentOf(ptr));
|
||||||
|
ptr = _root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> sibling = LeftOf(ParentOf(ptr));
|
||||||
|
|
||||||
|
if (ColorOf(sibling) == Red)
|
||||||
|
{
|
||||||
|
SetColor(sibling, Black);
|
||||||
|
SetColor(ParentOf(ptr), Red);
|
||||||
|
RotateRight(ParentOf(ptr));
|
||||||
|
sibling = LeftOf(ParentOf(ptr));
|
||||||
|
}
|
||||||
|
if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black)
|
||||||
|
{
|
||||||
|
SetColor(sibling, Red);
|
||||||
|
ptr = ParentOf(ptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ColorOf(LeftOf(sibling)) == Black)
|
||||||
|
{
|
||||||
|
SetColor(RightOf(sibling), Black);
|
||||||
|
SetColor(sibling, Red);
|
||||||
|
RotateLeft(sibling);
|
||||||
|
sibling = LeftOf(ParentOf(ptr));
|
||||||
|
}
|
||||||
|
SetColor(sibling, ColorOf(ParentOf(ptr)));
|
||||||
|
SetColor(ParentOf(ptr), Black);
|
||||||
|
SetColor(LeftOf(sibling), Black);
|
||||||
|
RotateRight(ParentOf(ptr));
|
||||||
|
ptr = _root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetColor(ptr, Black);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreBalanceAfterInsertion(IntervalTreeNode<K, V> balanceNode)
|
||||||
|
{
|
||||||
|
SetColor(balanceNode, Red);
|
||||||
|
while (balanceNode != null && balanceNode != _root && ColorOf(ParentOf(balanceNode)) == Red)
|
||||||
|
{
|
||||||
|
if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode))))
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> sibling = RightOf(ParentOf(ParentOf(balanceNode)));
|
||||||
|
|
||||||
|
if (ColorOf(sibling) == Red)
|
||||||
|
{
|
||||||
|
SetColor(ParentOf(balanceNode), Black);
|
||||||
|
SetColor(sibling, Black);
|
||||||
|
SetColor(ParentOf(ParentOf(balanceNode)), Red);
|
||||||
|
balanceNode = ParentOf(ParentOf(balanceNode));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (balanceNode == RightOf(ParentOf(balanceNode)))
|
||||||
|
{
|
||||||
|
balanceNode = ParentOf(balanceNode);
|
||||||
|
RotateLeft(balanceNode);
|
||||||
|
}
|
||||||
|
SetColor(ParentOf(balanceNode), Black);
|
||||||
|
SetColor(ParentOf(ParentOf(balanceNode)), Red);
|
||||||
|
RotateRight(ParentOf(ParentOf(balanceNode)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> sibling = LeftOf(ParentOf(ParentOf(balanceNode)));
|
||||||
|
|
||||||
|
if (ColorOf(sibling) == Red)
|
||||||
|
{
|
||||||
|
SetColor(ParentOf(balanceNode), Black);
|
||||||
|
SetColor(sibling, Black);
|
||||||
|
SetColor(ParentOf(ParentOf(balanceNode)), Red);
|
||||||
|
balanceNode = ParentOf(ParentOf(balanceNode));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (balanceNode == LeftOf(ParentOf(balanceNode)))
|
||||||
|
{
|
||||||
|
balanceNode = ParentOf(balanceNode);
|
||||||
|
RotateRight(balanceNode);
|
||||||
|
}
|
||||||
|
SetColor(ParentOf(balanceNode), Black);
|
||||||
|
SetColor(ParentOf(ParentOf(balanceNode)), Red);
|
||||||
|
RotateLeft(ParentOf(ParentOf(balanceNode)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetColor(_root, Black);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateLeft(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> right = RightOf(node);
|
||||||
|
node.Right = LeftOf(right);
|
||||||
|
if (node.Right != null)
|
||||||
|
{
|
||||||
|
node.Right.Parent = node;
|
||||||
|
}
|
||||||
|
IntervalTreeNode<K, V> nodeParent = ParentOf(node);
|
||||||
|
right.Parent = nodeParent;
|
||||||
|
if (nodeParent == null)
|
||||||
|
{
|
||||||
|
_root = right;
|
||||||
|
}
|
||||||
|
else if (node == LeftOf(nodeParent))
|
||||||
|
{
|
||||||
|
nodeParent.Left = right;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeParent.Right = right;
|
||||||
|
}
|
||||||
|
right.Left = node;
|
||||||
|
node.Parent = right;
|
||||||
|
|
||||||
|
PropagateFull(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateRight(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
IntervalTreeNode<K, V> left = LeftOf(node);
|
||||||
|
node.Left = RightOf(left);
|
||||||
|
if (node.Left != null)
|
||||||
|
{
|
||||||
|
node.Left.Parent = node;
|
||||||
|
}
|
||||||
|
IntervalTreeNode<K, V> nodeParent = ParentOf(node);
|
||||||
|
left.Parent = nodeParent;
|
||||||
|
if (nodeParent == null)
|
||||||
|
{
|
||||||
|
_root = left;
|
||||||
|
}
|
||||||
|
else if (node == RightOf(nodeParent))
|
||||||
|
{
|
||||||
|
nodeParent.Right = left;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeParent.Left = left;
|
||||||
|
}
|
||||||
|
left.Right = node;
|
||||||
|
node.Parent = left;
|
||||||
|
|
||||||
|
PropagateFull(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Safety-Methods
|
||||||
|
|
||||||
|
// These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the color of <paramref name="node"/>, or Black if it is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node</param>
|
||||||
|
/// <returns>The boolean color of <paramref name="node"/>, or black if null</returns>
|
||||||
|
private static bool ColorOf(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
return node == null || node.Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the color of <paramref name="node"/> node to <paramref name="color"/>.
|
||||||
|
/// <br></br>
|
||||||
|
/// This method does nothing if <paramref name="node"/> is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to set the color of</param>
|
||||||
|
/// <param name="color">Color (Boolean)</param>
|
||||||
|
private static void SetColor(IntervalTreeNode<K, V> node, bool color)
|
||||||
|
{
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
node.Color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method returns the left node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to retrieve the left child from</param>
|
||||||
|
/// <returns>Left child of <paramref name="node"/></returns>
|
||||||
|
private static IntervalTreeNode<K, V> LeftOf(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
return node?.Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method returns the right node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to retrieve the right child from</param>
|
||||||
|
/// <returns>Right child of <paramref name="node"/></returns>
|
||||||
|
private static IntervalTreeNode<K, V> RightOf(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
return node?.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the parent node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to retrieve the parent from</param>
|
||||||
|
/// <returns>Parent of <paramref name="node"/></returns>
|
||||||
|
private static IntervalTreeNode<K, V> ParentOf(IntervalTreeNode<K, V> node)
|
||||||
|
{
|
||||||
|
return node?.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public bool ContainsKey(K key)
|
||||||
|
{
|
||||||
|
return GetNode(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_root = null;
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="K">Key type of the node</typeparam>
|
||||||
|
/// <typeparam name="V">Value type of the node</typeparam>
|
||||||
|
internal class IntervalTreeNode<K, V>
|
||||||
|
{
|
||||||
|
internal bool Color = true;
|
||||||
|
internal IntervalTreeNode<K, V> Left = null;
|
||||||
|
internal IntervalTreeNode<K, V> Right = null;
|
||||||
|
internal IntervalTreeNode<K, V> Parent = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The start of the range.
|
||||||
|
/// </summary>
|
||||||
|
internal K Start;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The end of the range.
|
||||||
|
/// </summary>
|
||||||
|
internal K End;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum end value of this node and all its children.
|
||||||
|
/// </summary>
|
||||||
|
internal K Max;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value stored on this node.
|
||||||
|
/// </summary>
|
||||||
|
internal V Value;
|
||||||
|
|
||||||
|
public IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
|
||||||
|
{
|
||||||
|
this.Start = start;
|
||||||
|
this.End = end;
|
||||||
|
this.Max = end;
|
||||||
|
this.Value = value;
|
||||||
|
this.Parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 = 3061; //! 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";
|
||||||
@ -585,7 +585,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
translator.RegisterFunction(infoEntry.Address, func);
|
translator.RegisterFunction(infoEntry.Address, func);
|
||||||
|
|
||||||
bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, func);
|
bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, infoEntry.GuestSize, func);
|
||||||
|
|
||||||
Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
|
Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
|
||||||
}
|
}
|
||||||
@ -815,7 +815,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq);
|
TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq);
|
||||||
|
|
||||||
bool isAddressUnique = translator.Functions.TryAdd(address, func);
|
bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func);
|
||||||
|
|
||||||
Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique.");
|
Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique.");
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
|
return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(ConcurrentDictionary<ulong, TranslatedFunction> funcs)
|
internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
|
||||||
{
|
{
|
||||||
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();
|
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -49,7 +49,7 @@ namespace ARMeilleure.Translation
|
|||||||
private readonly AutoResetEvent _backgroundTranslatorEvent;
|
private readonly AutoResetEvent _backgroundTranslatorEvent;
|
||||||
private readonly ReaderWriterLock _backgroundTranslatorLock;
|
private readonly ReaderWriterLock _backgroundTranslatorLock;
|
||||||
|
|
||||||
internal ConcurrentDictionary<ulong, TranslatedFunction> Functions { get; }
|
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
||||||
internal AddressTable<ulong> FunctionTable { get; }
|
internal AddressTable<ulong> FunctionTable { get; }
|
||||||
internal EntryTable<uint> CountTable { get; }
|
internal EntryTable<uint> CountTable { get; }
|
||||||
internal TranslatorStubs Stubs { get; }
|
internal TranslatorStubs Stubs { get; }
|
||||||
@ -75,7 +75,7 @@ namespace ARMeilleure.Translation
|
|||||||
JitCache.Initialize(allocator);
|
JitCache.Initialize(allocator);
|
||||||
|
|
||||||
CountTable = new EntryTable<uint>();
|
CountTable = new EntryTable<uint>();
|
||||||
Functions = new ConcurrentDictionary<ulong, TranslatedFunction>();
|
Functions = new TranslatorCache<TranslatedFunction>();
|
||||||
FunctionTable = new AddressTable<ulong>(for64Bits ? Levels64Bit : Levels32Bit);
|
FunctionTable = new AddressTable<ulong>(for64Bits ? Levels64Bit : Levels32Bit);
|
||||||
Stubs = new TranslatorStubs(this);
|
Stubs = new TranslatorStubs(this);
|
||||||
|
|
||||||
@ -93,12 +93,12 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
_backgroundTranslatorLock.AcquireReaderLock(Timeout.Infinite);
|
_backgroundTranslatorLock.AcquireReaderLock(Timeout.Infinite);
|
||||||
|
|
||||||
if (_backgroundStack.TryPop(out RejitRequest request) &&
|
if (_backgroundStack.TryPop(out RejitRequest request) &&
|
||||||
_backgroundSet.TryRemove(request.Address, out _))
|
_backgroundSet.TryRemove(request.Address, out _))
|
||||||
{
|
{
|
||||||
TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true);
|
TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true);
|
||||||
|
|
||||||
Functions.AddOrUpdate(request.Address, func, (key, oldFunc) =>
|
Functions.AddOrUpdate(request.Address, func.GuestSize, func, (key, oldFunc) =>
|
||||||
{
|
{
|
||||||
EnqueueForDeletion(key, oldFunc);
|
EnqueueForDeletion(key, oldFunc);
|
||||||
return func;
|
return func;
|
||||||
@ -196,7 +196,7 @@ namespace ARMeilleure.Translation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong ExecuteSingle(State.ExecutionContext context, ulong address)
|
private ulong ExecuteSingle(State.ExecutionContext context, ulong address)
|
||||||
{
|
{
|
||||||
TranslatedFunction func = GetOrTranslate(address, context.ExecutionMode);
|
TranslatedFunction func = GetOrTranslate(address, context.ExecutionMode);
|
||||||
|
|
||||||
@ -209,13 +209,24 @@ 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))
|
||||||
{
|
{
|
||||||
func = Translate(address, mode, highCq: false);
|
func = Translate(address, mode, highCq: false);
|
||||||
|
|
||||||
TranslatedFunction oldFunc = Functions.GetOrAdd(address, func);
|
TranslatedFunction oldFunc = Functions.GetOrAdd(address, func.GuestSize, func);
|
||||||
|
|
||||||
if (oldFunc != func)
|
if (oldFunc != 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);
|
||||||
|
|
||||||
@ -471,7 +482,24 @@ namespace ARMeilleure.Translation
|
|||||||
// If rejit is running, stop it as it may be trying to rejit a function on the invalidated region.
|
// If rejit is running, stop it as it may be trying to rejit a function on the invalidated region.
|
||||||
ClearRejitQueue(allowRequeue: true);
|
ClearRejitQueue(allowRequeue: true);
|
||||||
|
|
||||||
// TODO: Completely remove functions overlapping the specified range from the cache.
|
ulong[] overlapAddresses = Array.Empty<ulong>();
|
||||||
|
|
||||||
|
int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses);
|
||||||
|
|
||||||
|
for (int index = 0; index < overlapsCount; index++)
|
||||||
|
{
|
||||||
|
ulong overlapAddress = overlapAddresses[index];
|
||||||
|
|
||||||
|
if (Functions.TryGetValue(overlapAddress, out TranslatedFunction overlap))
|
||||||
|
{
|
||||||
|
Functions.Remove(overlapAddress);
|
||||||
|
Volatile.Write(ref FunctionTable.GetValue(overlapAddress), FunctionTable.Fill);
|
||||||
|
EnqueueForDeletion(overlapAddress, overlap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove overlapping functions from the JitCache aswell.
|
||||||
|
// This should be done safely, with a mechanism to ensure the function is not being executed.
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EnqueueForRejit(ulong guestAddress, ExecutionMode mode)
|
internal void EnqueueForRejit(ulong guestAddress, ExecutionMode mode)
|
||||||
@ -493,7 +521,9 @@ namespace ARMeilleure.Translation
|
|||||||
// Ensure no attempt will be made to compile new functions due to rejit.
|
// Ensure no attempt will be made to compile new functions due to rejit.
|
||||||
ClearRejitQueue(allowRequeue: false);
|
ClearRejitQueue(allowRequeue: false);
|
||||||
|
|
||||||
foreach (var func in Functions.Values)
|
List<TranslatedFunction> functions = Functions.AsList();
|
||||||
|
|
||||||
|
foreach (var func in functions)
|
||||||
{
|
{
|
||||||
JitCache.Unmap(func.FuncPtr);
|
JitCache.Unmap(func.FuncPtr);
|
||||||
|
|
||||||
|
95
ARMeilleure/Translation/TranslatorCache.cs
Normal file
95
ARMeilleure/Translation/TranslatorCache.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace ARMeilleure.Translation
|
||||||
|
{
|
||||||
|
internal class TranslatorCache<T>
|
||||||
|
{
|
||||||
|
private readonly IntervalTree<ulong, T> _tree;
|
||||||
|
private readonly ReaderWriterLock _treeLock;
|
||||||
|
|
||||||
|
public int Count => _tree.Count;
|
||||||
|
|
||||||
|
public TranslatorCache()
|
||||||
|
{
|
||||||
|
_tree = new IntervalTree<ulong, T>();
|
||||||
|
_treeLock = new ReaderWriterLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryAdd(ulong address, ulong size, T value)
|
||||||
|
{
|
||||||
|
return AddOrUpdate(address, size, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddOrUpdate(ulong address, ulong size, T value, Func<ulong, T, T> updateFactoryCallback)
|
||||||
|
{
|
||||||
|
_treeLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback);
|
||||||
|
_treeLock.ReleaseWriterLock();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetOrAdd(ulong address, ulong size, T value)
|
||||||
|
{
|
||||||
|
_treeLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
value = _tree.GetOrAdd(address, address + size, value);
|
||||||
|
_treeLock.ReleaseWriterLock();
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(ulong address)
|
||||||
|
{
|
||||||
|
_treeLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
bool removed = _tree.Remove(address) != 0;
|
||||||
|
_treeLock.ReleaseWriterLock();
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_treeLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
_tree.Clear();
|
||||||
|
_treeLock.ReleaseWriterLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(ulong address)
|
||||||
|
{
|
||||||
|
_treeLock.AcquireReaderLock(Timeout.Infinite);
|
||||||
|
bool result = _tree.ContainsKey(address);
|
||||||
|
_treeLock.ReleaseReaderLock();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(ulong address, out T value)
|
||||||
|
{
|
||||||
|
_treeLock.AcquireReaderLock(Timeout.Infinite);
|
||||||
|
bool result = _tree.TryGet(address, out value);
|
||||||
|
_treeLock.ReleaseReaderLock();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps)
|
||||||
|
{
|
||||||
|
_treeLock.AcquireReaderLock(Timeout.Infinite);
|
||||||
|
int count = _tree.Get(address, address + size, ref overlaps);
|
||||||
|
_treeLock.ReleaseReaderLock();
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> AsList()
|
||||||
|
{
|
||||||
|
_treeLock.AcquireReaderLock(Timeout.Infinite);
|
||||||
|
List<T> list = _tree.AsList();
|
||||||
|
_treeLock.ReleaseReaderLock();
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -84,6 +85,7 @@ namespace Ryujinx.Common.Configuration
|
|||||||
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
|
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
|
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
|
||||||
|
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
ServiceLm,
|
ServiceLm,
|
||||||
ServiceMii,
|
ServiceMii,
|
||||||
ServiceMm,
|
ServiceMm,
|
||||||
|
ServiceMnpp,
|
||||||
ServiceNfc,
|
ServiceNfc,
|
||||||
ServiceNfp,
|
ServiceNfp,
|
||||||
ServiceNgct,
|
ServiceNgct,
|
||||||
|
@ -9,6 +9,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
Error,
|
Error,
|
||||||
Guest,
|
Guest,
|
||||||
AccessLog,
|
AccessLog,
|
||||||
Notice
|
Notice,
|
||||||
|
Trace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
public static Log? Guest { get; private set; }
|
public static Log? Guest { get; private set; }
|
||||||
public static Log? AccessLog { get; private set; }
|
public static Log? AccessLog { get; private set; }
|
||||||
public static Log? Stub { get; private set; }
|
public static Log? Stub { get; private set; }
|
||||||
|
public static Log? Trace { get; private set; }
|
||||||
public static Log Notice { get; } // Always enabled
|
public static Log Notice { get; } // Always enabled
|
||||||
|
|
||||||
static Logger()
|
static Logger()
|
||||||
@ -117,6 +118,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
Error = new Log(LogLevel.Error);
|
Error = new Log(LogLevel.Error);
|
||||||
Warning = new Log(LogLevel.Warning);
|
Warning = new Log(LogLevel.Warning);
|
||||||
Info = new Log(LogLevel.Info);
|
Info = new Log(LogLevel.Info);
|
||||||
|
Trace = new Log(LogLevel.Trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestartTime()
|
public static void RestartTime()
|
||||||
@ -172,7 +174,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
|
|
||||||
public static IReadOnlyCollection<LogLevel> GetEnabledLevels()
|
public static IReadOnlyCollection<LogLevel> GetEnabledLevels()
|
||||||
{
|
{
|
||||||
var logs = new Log?[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub };
|
var logs = new Log?[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace };
|
||||||
List<LogLevel> levels = new List<LogLevel>(logs.Length);
|
List<LogLevel> levels = new List<LogLevel>(logs.Length);
|
||||||
foreach (var log in logs)
|
foreach (var log in logs)
|
||||||
{
|
{
|
||||||
@ -196,6 +198,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : new Log?(); break;
|
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : new Log?(); break;
|
||||||
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog): new Log?(); break;
|
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog): new Log?(); break;
|
||||||
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break;
|
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break;
|
||||||
|
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break;
|
||||||
default: throw new ArgumentException("Unknown Log Level");
|
default: throw new ArgumentException("Unknown Log Level");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
LogLevel.Error => ConsoleColor.Red,
|
LogLevel.Error => ConsoleColor.Red,
|
||||||
LogLevel.Stub => ConsoleColor.DarkGray,
|
LogLevel.Stub => ConsoleColor.DarkGray,
|
||||||
LogLevel.Notice => ConsoleColor.Cyan,
|
LogLevel.Notice => ConsoleColor.Cyan,
|
||||||
|
LogLevel.Trace => ConsoleColor.DarkCyan,
|
||||||
_ => ConsoleColor.Gray,
|
_ => ConsoleColor.Gray,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
namespace Ryujinx.Common
|
||||||
{
|
{
|
||||||
public static class BitUtils
|
public static class BitUtils
|
||||||
{
|
{
|
||||||
private static ReadOnlySpan<byte> ClzNibbleTbl => new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
||||||
|
|
||||||
public static uint AlignUp(uint value, int size)
|
public static uint AlignUp(uint value, int size)
|
||||||
{
|
{
|
||||||
return (uint)AlignUp((int)value, size);
|
return (uint)AlignUp((int)value, size);
|
||||||
@ -76,60 +75,7 @@ namespace Ryujinx.Common
|
|||||||
|
|
||||||
public static int Pow2RoundDown(int value)
|
public static int Pow2RoundDown(int value)
|
||||||
{
|
{
|
||||||
return IsPowerOfTwo32(value) ? value : Pow2RoundUp(value) >> 1;
|
return BitOperations.IsPow2(value) ? value : Pow2RoundUp(value) >> 1;
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsPowerOfTwo32(int value)
|
|
||||||
{
|
|
||||||
return value != 0 && (value & (value - 1)) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsPowerOfTwo64(long value)
|
|
||||||
{
|
|
||||||
return value != 0 && (value & (value - 1)) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int CountLeadingZeros32(int value)
|
|
||||||
{
|
|
||||||
return (int)CountLeadingZeros((ulong)value, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int CountLeadingZeros64(long value)
|
|
||||||
{
|
|
||||||
return (int)CountLeadingZeros((ulong)value, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong CountLeadingZeros(ulong value, int size)
|
|
||||||
{
|
|
||||||
if (value == 0ul)
|
|
||||||
{
|
|
||||||
return (ulong)size;
|
|
||||||
}
|
|
||||||
|
|
||||||
int nibbleIdx = size;
|
|
||||||
int preCount, count = 0;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
nibbleIdx -= 4;
|
|
||||||
preCount = ClzNibbleTbl[(int)(value >> nibbleIdx) & 0b1111];
|
|
||||||
count += preCount;
|
|
||||||
}
|
|
||||||
while (preCount == 4);
|
|
||||||
|
|
||||||
return (ulong)count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int CountTrailingZeros32(int value)
|
|
||||||
{
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
while (((value >> count) & 1) == 0)
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long ReverseBits64(long value)
|
public static long ReverseBits64(long value)
|
||||||
|
@ -28,5 +28,10 @@ namespace Ryujinx.Cpu
|
|||||||
{
|
{
|
||||||
_translator.Execute(context, address);
|
_translator.Execute(context, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
_translator.InvalidateJitCacheRegion(address, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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.
|
||||||
@ -658,7 +688,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool dataOverlaps = texture.DataOverlaps(overlap, compatibility);
|
bool dataOverlaps = texture.DataOverlaps(overlap, compatibility);
|
||||||
|
|
||||||
if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group))
|
if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group))
|
||||||
{
|
{
|
||||||
incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility));
|
incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility));
|
||||||
@ -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>
|
||||||
|
@ -236,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Synchronize memory for a given texture.
|
/// Synchronize memory for a given texture.
|
||||||
/// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
|
/// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="texture">The texture being used</param>
|
/// <param name="texture">The texture being used</param>
|
||||||
@ -280,7 +280,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
// Evaluate if any copy dependencies need to be fulfilled. A few rules:
|
// Evaluate if any copy dependencies need to be fulfilled. A few rules:
|
||||||
// If the copy handle needs to be synchronized, prefer our own state.
|
// If the copy handle needs to be synchronized, prefer our own state.
|
||||||
// If we need to be synchronized and there is a copy present, prefer the copy.
|
// If we need to be synchronized and there is a copy present, prefer the copy.
|
||||||
|
|
||||||
if (group.NeedsCopy && group.Copy(_context))
|
if (group.NeedsCopy && group.Copy(_context))
|
||||||
{
|
{
|
||||||
@ -618,7 +618,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluate the range of tracking handles which a view texture overlaps with,
|
/// Evaluate the range of tracking handles which a view texture overlaps with,
|
||||||
/// using the view's position and slice/level counts.
|
/// using the view's position and slice/level counts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="firstLayer">The first layer of the texture</param>
|
/// <param name="firstLayer">The first layer of the texture</param>
|
||||||
@ -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);
|
||||||
@ -1277,7 +1286,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
TextureInfo info = Storage.Info;
|
TextureInfo info = Storage.Info;
|
||||||
TextureInfo otherInfo = other.Storage.Info;
|
TextureInfo otherInfo = other.Storage.Info;
|
||||||
|
|
||||||
if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
|
if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
|
||||||
TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel))
|
TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel))
|
||||||
{
|
{
|
||||||
// These textures are copy compatible. Create the dependency.
|
// These textures are copy compatible. Create the dependency.
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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();
|
||||||
|
@ -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,18 +395,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
MemoryRange currentRange = range.GetSubRange(i);
|
MemoryRange currentRange = range.GetSubRange(i);
|
||||||
|
|
||||||
ulong address = currentRange.Address & ~PageMask;
|
if (currentRange.Address != PteUnmapped)
|
||||||
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
|
|
||||||
|
|
||||||
while (address < endAddress)
|
|
||||||
{
|
{
|
||||||
if (Translate(va) != address)
|
ulong address = currentRange.Address & ~PageMask;
|
||||||
{
|
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
va += PageSize;
|
while (address < endAddress)
|
||||||
address += PageSize;
|
{
|
||||||
|
if (Translate(va) != address)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
va += PageSize;
|
||||||
|
address += PageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ulong endVa = va + (((currentRange.Size) + PageMask) & ~PageMask);
|
||||||
|
|
||||||
|
while (va < endVa)
|
||||||
|
{
|
||||||
|
if (Translate(va) != PteUnmapped)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
va += PageSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,24 +99,28 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
if (range.Count == 1)
|
if (range.Count == 1)
|
||||||
{
|
{
|
||||||
var singleRange = range.GetSubRange(0);
|
var singleRange = range.GetSubRange(0);
|
||||||
return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
|
if (singleRange.Address != MemoryManager.PteUnmapped)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Span<byte> data = new byte[range.GetSize()];
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < range.Count; i++)
|
|
||||||
{
|
{
|
||||||
var currentRange = range.GetSubRange(i);
|
return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
|
||||||
int size = (int)currentRange.Size;
|
|
||||||
_cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size));
|
|
||||||
offset += size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Span<byte> data = new byte[range.GetSize()];
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < range.Count; i++)
|
||||||
|
{
|
||||||
|
var currentRange = range.GetSubRange(i);
|
||||||
|
int size = (int)currentRange.Size;
|
||||||
|
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
_cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size));
|
||||||
|
}
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -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,7 +255,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
if (range.Count == 1)
|
if (range.Count == 1)
|
||||||
{
|
{
|
||||||
var singleRange = range.GetSubRange(0);
|
var singleRange = range.GetSubRange(0);
|
||||||
writeCallback(singleRange.Address, data);
|
if (singleRange.Address != MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
writeCallback(singleRange.Address, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -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;
|
||||||
writeCallback(currentRange.Address, data.Slice(offset, size));
|
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
@ -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);
|
||||||
|
@ -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 = 3063;
|
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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
@ -369,7 +379,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
inst &= Instruction.Mask;
|
inst &= Instruction.Mask;
|
||||||
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
||||||
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
||||||
bool accurateType = inst != Instruction.Lod;
|
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
|
||||||
bool coherent = flags.HasFlag(TextureFlags.Coherent);
|
bool coherent = flags.HasFlag(TextureFlags.Coherent);
|
||||||
|
|
||||||
if (isImage)
|
if (isImage)
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitializeOutputComponent(context, AttributeConsts.UserAttributeBase + index * 4, perPatch: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
int usedAttributesPerPatch = context.Config.UsedOutputAttributesPerPatch;
|
UInt128 usedAttributesPerPatch = context.Config.NextInputAttributesPerPatchComponents;
|
||||||
while (usedAttributesPerPatch != 0)
|
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++)
|
||||||
|
112
Ryujinx.Graphics.Shader/Translation/UInt128.cs
Normal file
112
Ryujinx.Graphics.Shader/Translation/UInt128.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
using static Ryujinx.Graphics.Texture.BlockLinearConstants;
|
using static Ryujinx.Graphics.Texture.BlockLinearConstants;
|
||||||
@ -47,15 +48,15 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
{
|
{
|
||||||
_texBpp = bpp;
|
_texBpp = bpp;
|
||||||
|
|
||||||
_bppShift = BitUtils.CountTrailingZeros32(bpp);
|
_bppShift = BitOperations.TrailingZeroCount(bpp);
|
||||||
|
|
||||||
_bhMask = gobBlocksInY - 1;
|
_bhMask = gobBlocksInY - 1;
|
||||||
_bdMask = gobBlocksInZ - 1;
|
_bdMask = gobBlocksInZ - 1;
|
||||||
|
|
||||||
_bhShift = BitUtils.CountTrailingZeros32(gobBlocksInY);
|
_bhShift = BitOperations.TrailingZeroCount(gobBlocksInY);
|
||||||
_bdShift = BitUtils.CountTrailingZeros32(gobBlocksInZ);
|
_bdShift = BitOperations.TrailingZeroCount(gobBlocksInZ);
|
||||||
|
|
||||||
_xShift = BitUtils.CountTrailingZeros32(GobSize * gobBlocksInY * gobBlocksInZ);
|
_xShift = BitOperations.TrailingZeroCount(GobSize * gobBlocksInY * gobBlocksInZ);
|
||||||
|
|
||||||
RobAndSliceSizes rsSizes = GetRobAndSliceSizes(width, height, gobBlocksInY, gobBlocksInZ);
|
RobAndSliceSizes rsSizes = GetRobAndSliceSizes(width, height, gobBlocksInY, gobBlocksInZ);
|
||||||
|
|
||||||
|
@ -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())
|
||||||
{
|
{
|
||||||
|
@ -36,6 +36,11 @@ namespace Ryujinx.HLE.HOS
|
|||||||
_cpuContext.Execute(context, codeAddress);
|
_cpuContext.Execute(context, codeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
_cpuContext.InvalidateCacheRegion(address, size);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_memoryManager is IRefCounted rc)
|
if (_memoryManager is IRefCounted rc)
|
||||||
|
@ -76,6 +76,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
internal ServerBase BsdServer { get; private set; }
|
internal ServerBase BsdServer { get; private set; }
|
||||||
internal ServerBase AudRenServer { get; private set; }
|
internal ServerBase AudRenServer { get; private set; }
|
||||||
internal ServerBase AudOutServer { get; private set; }
|
internal ServerBase AudOutServer { get; private set; }
|
||||||
|
internal ServerBase FsServer { get; private set; }
|
||||||
internal ServerBase HidServer { get; private set; }
|
internal ServerBase HidServer { get; private set; }
|
||||||
internal ServerBase NvDrvServer { get; private set; }
|
internal ServerBase NvDrvServer { get; private set; }
|
||||||
internal ServerBase TimeServer { get; private set; }
|
internal ServerBase TimeServer { get; private set; }
|
||||||
@ -298,6 +299,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
BsdServer = new ServerBase(KernelContext, "BsdServer");
|
BsdServer = new ServerBase(KernelContext, "BsdServer");
|
||||||
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
|
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
|
||||||
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
|
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
|
||||||
|
FsServer = new ServerBase(KernelContext, "FsServer");
|
||||||
HidServer = new ServerBase(KernelContext, "HidServer");
|
HidServer = new ServerBase(KernelContext, "HidServer");
|
||||||
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
|
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
|
||||||
TimeServer = new ServerBase(KernelContext, "TimeServer");
|
TimeServer = new ServerBase(KernelContext, "TimeServer");
|
||||||
@ -464,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();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Common
|
namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||||
{
|
{
|
||||||
@ -41,10 +42,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
range++;
|
range++;
|
||||||
|
|
||||||
// This is log2(Range) plus one.
|
// This is log2(Range) plus one.
|
||||||
int nextRangeLog2 = 64 - BitUtils.CountLeadingZeros64(range);
|
int nextRangeLog2 = 64 - BitOperations.LeadingZeroCount((ulong)range);
|
||||||
|
|
||||||
// If Range is already power of 2, subtract one to use log2(Range) directly.
|
// If Range is already power of 2, subtract one to use log2(Range) directly.
|
||||||
int rangeLog2 = nextRangeLog2 - (BitUtils.IsPowerOfTwo64(range) ? 1 : 0);
|
int rangeLog2 = nextRangeLog2 - (BitOperations.IsPow2(range) ? 1 : 0);
|
||||||
|
|
||||||
int parts = rangeLog2 > 32 ? 2 : 1;
|
int parts = rangeLog2 > 32 ? 2 : 1;
|
||||||
int bitsPerPart = rangeLog2 / parts;
|
int bitsPerPart = rangeLog2 / parts;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
{
|
{
|
||||||
@ -259,11 +260,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
|
|
||||||
if (backwards)
|
if (backwards)
|
||||||
{
|
{
|
||||||
index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
|
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
|
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,11 +313,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
|
|
||||||
if (backwards)
|
if (backwards)
|
||||||
{
|
{
|
||||||
index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
|
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
|
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,5 +9,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
IVirtualMemoryManager AddressSpace { get; }
|
IVirtualMemoryManager AddressSpace { get; }
|
||||||
|
|
||||||
void Execute(ExecutionContext context, ulong codeAddress);
|
void Execute(ExecutionContext context, ulong codeAddress);
|
||||||
|
void InvalidateCacheRegion(ulong address, ulong size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
{
|
{
|
||||||
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
{
|
{
|
||||||
int mask = _idMasks[index];
|
int mask = _idMasks[index];
|
||||||
|
|
||||||
int firstFreeBit = BitUtils.CountLeadingZeros32((mask + 1) & ~mask);
|
int firstFreeBit = BitOperations.LeadingZeroCount((uint)((mask + 1) & ~mask));
|
||||||
|
|
||||||
if (firstFreeBit < 32)
|
if (firstFreeBit < 32)
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
|
|||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
{
|
{
|
||||||
@ -130,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
return KernelResult.Success;
|
return KernelResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int codeMask = 1 << (32 - BitUtils.CountLeadingZeros32(code + 1));
|
int codeMask = 1 << (32 - BitOperations.LeadingZeroCount((uint)code + 1));
|
||||||
|
|
||||||
// Check if the property was already set.
|
// Check if the property was already set.
|
||||||
if (((mask0 & codeMask) & 0x1e008) != 0)
|
if (((mask0 & codeMask) & 0x1e008) != 0)
|
||||||
|
@ -18,6 +18,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -459,11 +459,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
|||||||
{
|
{
|
||||||
if (argValues != null)
|
if (argValues != null)
|
||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.KernelSvc, string.Format(formatOrSvcName, argValues));
|
Logger.Trace?.Print(LogClass.KernelSvc, string.Format(formatOrSvcName, argValues));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.KernelSvc, formatOrSvcName);
|
Logger.Trace?.Print(LogClass.KernelSvc, formatOrSvcName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,7 +479,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.KernelSvc, $"{svcName} returned result {result}.");
|
Logger.Trace?.Print(LogClass.KernelSvc, $"{svcName} returned result {result}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
@ -1371,7 +1368,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
PreferredCore = _originalPreferredCore;
|
PreferredCore = _originalPreferredCore;
|
||||||
AffinityMask = _originalAffinityMask;
|
AffinityMask = _originalAffinityMask;
|
||||||
|
|
||||||
if (AffinityMask != affinityMask)
|
if (AffinityMask != affinityMask)
|
||||||
{
|
{
|
||||||
if ((AffinityMask & 1UL << ActiveCore) != 0)
|
if ((AffinityMask & 1UL << ActiveCore) != 0)
|
||||||
|
@ -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);
|
||||||
|
@ -136,7 +136,8 @@ 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();
|
||||||
|
@ -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());
|
||||||
|
@ -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>();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -15,12 +15,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
// GetAppletResourceUserId() -> nn::applet::AppletResourceUserId
|
// GetAppletResourceUserId() -> nn::applet::AppletResourceUserId
|
||||||
public ResultCode GetAppletResourceUserId(ServiceCtx context)
|
public ResultCode GetAppletResourceUserId(ServiceCtx context)
|
||||||
{
|
{
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
|
||||||
|
|
||||||
long appletResourceUserId = context.Device.System.AppletState.AppletResourceUserIds.Add(_pid);
|
long appletResourceUserId = context.Device.System.AppletState.AppletResourceUserIds.Add(_pid);
|
||||||
|
|
||||||
context.ResponseData.Write(appletResourceUserId);
|
context.ResponseData.Write(appletResourceUserId);
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { appletResourceUserId });
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
{
|
{
|
||||||
private int _disposeState;
|
private int _disposeState;
|
||||||
|
|
||||||
|
public DisposableIpcService(ServerBase server = null) : base(server) { }
|
||||||
|
|
||||||
protected abstract void Dispose(bool isDisposing);
|
protected abstract void Dispose(bool isDisposing);
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -132,22 +132,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result ReadFsPath(out FsPath path, ServiceCtx context, int index = 0)
|
|
||||||
{
|
|
||||||
ulong position = context.Request.PtrBuff[index].Position;
|
|
||||||
ulong size = context.Request.PtrBuff[index].Size;
|
|
||||||
|
|
||||||
byte[] pathBytes = new byte[size];
|
|
||||||
|
|
||||||
context.Memory.Read(position, pathBytes);
|
|
||||||
|
|
||||||
return FsPath.FromSpan(out path, pathBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ref readonly FspPath GetFspPath(ServiceCtx context, int index = 0)
|
public static ref readonly FspPath GetFspPath(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<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);
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
|||||||
{
|
{
|
||||||
private SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> _baseFileSystemProxy;
|
private SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> _baseFileSystemProxy;
|
||||||
|
|
||||||
public IFileSystemProxy(ServiceCtx context)
|
public IFileSystemProxy(ServiceCtx context) : base(context.Device.System.FsServer)
|
||||||
{
|
{
|
||||||
var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient;
|
var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient;
|
||||||
_baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject();
|
_baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject();
|
||||||
|
@ -24,6 +24,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
private bool _vibrationPermitted;
|
private bool _vibrationPermitted;
|
||||||
private bool _usbFullKeyControllerEnabled;
|
private bool _usbFullKeyControllerEnabled;
|
||||||
private bool _isFirmwareUpdateAvailableForSixAxisSensor;
|
private bool _isFirmwareUpdateAvailableForSixAxisSensor;
|
||||||
|
private bool _isSixAxisSensorUnalteredPassthroughEnabled;
|
||||||
|
|
||||||
private HidNpadJoyAssignmentMode _npadJoyAssignmentMode;
|
private HidNpadJoyAssignmentMode _npadJoyAssignmentMode;
|
||||||
private HidNpadHandheldActivationMode _npadHandheldActivationMode;
|
private HidNpadHandheldActivationMode _npadHandheldActivationMode;
|
||||||
@ -335,7 +336,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// StartSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
// StartSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode StartSixAxisSensor(ServiceCtx context)
|
public ResultCode StartSixAxisSensor(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
|
||||||
@ -347,7 +349,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// StopSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
// StopSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode StopSixAxisSensor(ServiceCtx context)
|
public ResultCode StopSixAxisSensor(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
|
||||||
@ -359,7 +362,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// IsSixAxisSensorFusionEnabled(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsEnabled
|
// IsSixAxisSensorFusionEnabled(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsEnabled
|
||||||
public ResultCode IsSixAxisSensorFusionEnabled(ServiceCtx context)
|
public ResultCode IsSixAxisSensorFusionEnabled(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
context.ResponseData.Write(_sixAxisSensorFusionEnabled);
|
context.ResponseData.Write(_sixAxisSensorFusionEnabled);
|
||||||
@ -373,9 +377,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// EnableSixAxisSensorFusion(bool Enabled, nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
// EnableSixAxisSensorFusion(bool Enabled, nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode EnableSixAxisSensorFusion(ServiceCtx context)
|
public ResultCode EnableSixAxisSensorFusion(ServiceCtx context)
|
||||||
{
|
{
|
||||||
_sixAxisSensorFusionEnabled = context.RequestData.ReadBoolean();
|
_sixAxisSensorFusionEnabled = context.RequestData.ReadUInt32() != 0;
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled });
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled });
|
||||||
|
|
||||||
@ -386,7 +390,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// SetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, float RevisePower, float ReviseRange, nn::applet::AppletResourceUserId)
|
// SetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, float RevisePower, float ReviseRange, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode SetSixAxisSensorFusionParameters(ServiceCtx context)
|
public ResultCode SetSixAxisSensorFusionParameters(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
|
|
||||||
_sensorFusionParams = new HidSensorFusionParameters
|
_sensorFusionParams = new HidSensorFusionParameters
|
||||||
{
|
{
|
||||||
@ -405,7 +410,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// GetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float RevisePower, float ReviseRange)
|
// GetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float RevisePower, float ReviseRange)
|
||||||
public ResultCode GetSixAxisSensorFusionParameters(ServiceCtx context)
|
public ResultCode GetSixAxisSensorFusionParameters(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
context.ResponseData.Write(_sensorFusionParams.RevisePower);
|
context.ResponseData.Write(_sensorFusionParams.RevisePower);
|
||||||
@ -420,7 +426,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// ResetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
// ResetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode ResetSixAxisSensorFusionParameters(ServiceCtx context)
|
public ResultCode ResetSixAxisSensorFusionParameters(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
_sensorFusionParams.RevisePower = 0;
|
_sensorFusionParams.RevisePower = 0;
|
||||||
@ -436,6 +443,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
public ResultCode SetAccelerometerParameters(ServiceCtx context)
|
public ResultCode SetAccelerometerParameters(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
|
|
||||||
_accelerometerParams = new HidAccelerometerParameters
|
_accelerometerParams = new HidAccelerometerParameters
|
||||||
{
|
{
|
||||||
@ -454,7 +462,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// GetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float X, float Y
|
// GetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float X, float Y
|
||||||
public ResultCode GetAccelerometerParameters(ServiceCtx context)
|
public ResultCode GetAccelerometerParameters(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
context.ResponseData.Write(_accelerometerParams.X);
|
context.ResponseData.Write(_accelerometerParams.X);
|
||||||
@ -469,7 +478,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// ResetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
// ResetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode ResetAccelerometerParameters(ServiceCtx context)
|
public ResultCode ResetAccelerometerParameters(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
_accelerometerParams.X = 0;
|
_accelerometerParams.X = 0;
|
||||||
@ -484,9 +494,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// SetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, uint PlayMode, nn::applet::AppletResourceUserId)
|
// SetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, uint PlayMode, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode SetAccelerometerPlayMode(ServiceCtx context)
|
public ResultCode SetAccelerometerPlayMode(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
_accelerometerPlayMode = context.RequestData.ReadUInt32();
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
_accelerometerPlayMode = context.RequestData.ReadUInt32();
|
||||||
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode });
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode });
|
||||||
|
|
||||||
@ -497,7 +508,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// GetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> uint PlayMode
|
// GetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> uint PlayMode
|
||||||
public ResultCode GetAccelerometerPlayMode(ServiceCtx context)
|
public ResultCode GetAccelerometerPlayMode(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
context.ResponseData.Write(_accelerometerPlayMode);
|
context.ResponseData.Write(_accelerometerPlayMode);
|
||||||
@ -511,7 +523,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// ResetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
// ResetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode ResetAccelerometerPlayMode(ServiceCtx context)
|
public ResultCode ResetAccelerometerPlayMode(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
_accelerometerPlayMode = 0;
|
_accelerometerPlayMode = 0;
|
||||||
@ -525,9 +538,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// SetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, uint GyroscopeZeroDriftMode, nn::applet::AppletResourceUserId)
|
// SetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, uint GyroscopeZeroDriftMode, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context)
|
public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
_gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32();
|
_gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32();
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
|
||||||
|
|
||||||
@ -538,7 +551,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// GetGyroscopeZeroDriftMode(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> int GyroscopeZeroDriftMode
|
// GetGyroscopeZeroDriftMode(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> int GyroscopeZeroDriftMode
|
||||||
public ResultCode GetGyroscopeZeroDriftMode(ServiceCtx context)
|
public ResultCode GetGyroscopeZeroDriftMode(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
context.ResponseData.Write((int)_gyroscopeZeroDriftMode);
|
context.ResponseData.Write((int)_gyroscopeZeroDriftMode);
|
||||||
@ -552,7 +566,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// ResetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
// ResetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||||
public ResultCode ResetGyroscopeZeroDriftMode(ServiceCtx context)
|
public ResultCode ResetGyroscopeZeroDriftMode(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
|
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
|
||||||
@ -566,7 +581,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
|
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
|
||||||
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
|
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
bool isAtRest = true;
|
bool isAtRest = true;
|
||||||
@ -582,8 +598,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// IsFirmwareUpdateAvailableForSixAxisSensor(nn::hid::AppletResourceUserId, nn::hid::SixAxisSensorHandle, pid) -> bool UpdateAvailable
|
// IsFirmwareUpdateAvailableForSixAxisSensor(nn::hid::AppletResourceUserId, nn::hid::SixAxisSensorHandle, pid) -> bool UpdateAvailable
|
||||||
public ResultCode IsFirmwareUpdateAvailableForSixAxisSensor(ServiceCtx context)
|
public ResultCode IsFirmwareUpdateAvailableForSixAxisSensor(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
context.RequestData.BaseStream.Position += 4;
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
|
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
|
||||||
@ -593,6 +609,64 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandHipc(84)] // 13.0.0+
|
||||||
|
// EnableSixAxisSensorUnalteredPassthrough(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle, u8 enabled)
|
||||||
|
public ResultCode EnableSixAxisSensorUnalteredPassthrough(ServiceCtx context)
|
||||||
|
{
|
||||||
|
_isSixAxisSensorUnalteredPassthroughEnabled = context.RequestData.ReadUInt32() != 0;
|
||||||
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isSixAxisSensorUnalteredPassthroughEnabled });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandHipc(85)] // 13.0.0+
|
||||||
|
// IsSixAxisSensorUnalteredPassthroughEnabled(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> u8 enabled
|
||||||
|
public ResultCode IsSixAxisSensorUnalteredPassthroughEnabled(ServiceCtx context)
|
||||||
|
{
|
||||||
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
context.ResponseData.Write(_isSixAxisSensorUnalteredPassthroughEnabled);
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandHipc(87)] // 13.0.0+
|
||||||
|
// LoadSixAxisSensorCalibrationParameter(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle, u64 unknown)
|
||||||
|
public ResultCode LoadSixAxisSensorCalibrationParameter(ServiceCtx context)
|
||||||
|
{
|
||||||
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
// TODO: CalibrationParameter have to be determined.
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandHipc(88)] // 13.0.0+
|
||||||
|
// GetSixAxisSensorIcInformation(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> u64 unknown
|
||||||
|
public ResultCode GetSixAxisSensorIcInformation(ServiceCtx context)
|
||||||
|
{
|
||||||
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
// TODO: IcInformation have to be determined.
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandHipc(91)]
|
[CommandHipc(91)]
|
||||||
// ActivateGesture(nn::applet::AppletResourceUserId, int Unknown0)
|
// ActivateGesture(nn::applet::AppletResourceUserId, int Unknown0)
|
||||||
public ResultCode ActivateGesture(ServiceCtx context)
|
public ResultCode ActivateGesture(ServiceCtx context)
|
||||||
@ -606,16 +680,15 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
}
|
}
|
||||||
|
|
||||||
[CommandHipc(100)]
|
[CommandHipc(100)]
|
||||||
// SetSupportedNpadStyleSet(nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag)
|
// SetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag)
|
||||||
public ResultCode SetSupportedNpadStyleSet(ServiceCtx context)
|
public ResultCode SetSupportedNpadStyleSet(ServiceCtx context)
|
||||||
{
|
{
|
||||||
|
ulong pid = context.Request.HandleDesc.PId;
|
||||||
ControllerType type = (ControllerType)context.RequestData.ReadInt32();
|
ControllerType type = (ControllerType)context.RequestData.ReadInt32();
|
||||||
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new {
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, type });
|
||||||
appletResourceUserId,
|
|
||||||
type
|
|
||||||
});
|
|
||||||
|
|
||||||
context.Device.Hid.Npads.SupportedStyleSets = type;
|
context.Device.Hid.Npads.SupportedStyleSets = type;
|
||||||
|
|
||||||
@ -623,17 +696,15 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
}
|
}
|
||||||
|
|
||||||
[CommandHipc(101)]
|
[CommandHipc(101)]
|
||||||
// GetSupportedNpadStyleSet(nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag
|
// GetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag
|
||||||
public ResultCode GetSupportedNpadStyleSet(ServiceCtx context)
|
public ResultCode GetSupportedNpadStyleSet(ServiceCtx context)
|
||||||
{
|
{
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
ulong pid = context.Request.HandleDesc.PId;
|
||||||
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
context.ResponseData.Write((int)context.Device.Hid.Npads.SupportedStyleSets);
|
context.ResponseData.Write((int)context.Device.Hid.Npads.SupportedStyleSets);
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new {
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, context.Device.Hid.Npads.SupportedStyleSets });
|
||||||
appletResourceUserId,
|
|
||||||
context.Device.Hid.Npads.SupportedStyleSets
|
|
||||||
});
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
@ -658,7 +729,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"{supportedPlayerIds.Length} " + string.Join(",", supportedPlayerIds.ToArray()));
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"{supportedPlayerIds.Length} Players: " + string.Join(",", supportedPlayerIds.ToArray()));
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
|
|
||||||
if (serviceExists)
|
if (serviceExists)
|
||||||
{
|
{
|
||||||
Logger.Debug?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}");
|
Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}");
|
||||||
|
|
||||||
result = (ResultCode)processRequest.Invoke(service, new object[] { context });
|
result = (ResultCode)processRequest.Invoke(service, new object[] { context });
|
||||||
}
|
}
|
||||||
|
63
Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs
Normal file
63
Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Cpu;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Mnpp
|
||||||
|
{
|
||||||
|
[Service("mnpp:app")] // 13.0.0+
|
||||||
|
class IServiceForApplication : IpcService
|
||||||
|
{
|
||||||
|
public IServiceForApplication(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandHipc(0)]
|
||||||
|
// Initialize(pid)
|
||||||
|
public ResultCode Initialize(ServiceCtx context)
|
||||||
|
{
|
||||||
|
// Pid placeholder
|
||||||
|
context.RequestData.ReadInt64();
|
||||||
|
ulong pid = context.Request.HandleDesc.PId;
|
||||||
|
|
||||||
|
// TODO: Service calls set:sys GetPlatformRegion.
|
||||||
|
// If the result == 1 (China) it calls arp:r GetApplicationInstanceId and GetApplicationLaunchProperty to get the title id and store it internally.
|
||||||
|
// If not, it does nothing.
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { pid });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandHipc(1)]
|
||||||
|
// SendRawTelemetryData(nn::account::Uid user_id, buffer<bytes, 5> title_id)
|
||||||
|
public ResultCode SendRawTelemetryData(ServiceCtx context)
|
||||||
|
{
|
||||||
|
ulong titleIdInputPosition = context.Request.SendBuff[0].Position;
|
||||||
|
ulong titleIdInputSize = context.Request.SendBuff[0].Size;
|
||||||
|
|
||||||
|
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||||
|
|
||||||
|
// TODO: Service calls set:sys GetPlatformRegion.
|
||||||
|
// If the result != 1 (China) it returns ResultCode.Success.
|
||||||
|
|
||||||
|
if (userId.IsNull)
|
||||||
|
{
|
||||||
|
return ResultCode.InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (titleIdInputSize <= 64)
|
||||||
|
{
|
||||||
|
string titleId = MemoryHelper.ReadAsciiString(context.Memory, titleIdInputPosition, (long)titleIdInputSize);
|
||||||
|
|
||||||
|
// TODO: The service stores the titleId internally and seems proceed to some telemetry for China, which is not needed here.
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { userId, titleId });
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { userId });
|
||||||
|
|
||||||
|
return ResultCode.InvalidBufferSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs
Normal file
13
Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace Ryujinx.HLE.HOS.Services.Mnpp
|
||||||
|
{
|
||||||
|
enum ResultCode
|
||||||
|
{
|
||||||
|
ModuleId = 239,
|
||||||
|
ErrorCodeShift = 9,
|
||||||
|
|
||||||
|
Success = 0,
|
||||||
|
|
||||||
|
InvalidArgument = (100 << ErrorCodeShift) | ModuleId,
|
||||||
|
InvalidBufferSize = (101 << ErrorCodeShift) | ModuleId
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,15 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
[Service("olsc:u")] // 10.0.0+
|
[Service("olsc:u")] // 10.0.0+
|
||||||
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 });
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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));
|
||||||
|
@ -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));
|
||||||
|
@ -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
|
||||||
|
@ -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,16 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
|||||||
{
|
{
|
||||||
private readonly ViServiceType _serviceType;
|
private readonly ViServiceType _serviceType;
|
||||||
|
|
||||||
private readonly List<DisplayInfo> _displayInfo;
|
private class DisplayState
|
||||||
private readonly Dictionary<ulong, DisplayInfo> _openDisplayInfo;
|
{
|
||||||
|
public int RetrievedEventsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<DisplayInfo> _displayInfo;
|
||||||
|
private readonly Dictionary<ulong, DisplayState> _openDisplays;
|
||||||
|
|
||||||
private int _vsyncEventHandle;
|
private int _vsyncEventHandle;
|
||||||
|
|
||||||
public IApplicationDisplayService(ViServiceType serviceType)
|
public IApplicationDisplayService(ViServiceType serviceType)
|
||||||
{
|
{
|
||||||
_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;
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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; }
|
||||||
@ -26,8 +26,8 @@ namespace Ryujinx.HLE.Loaders.Executables
|
|||||||
public uint DataSize { get; }
|
public uint DataSize { get; }
|
||||||
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();
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user