Merge branch 'master' into aot

This commit is contained in:
LDj3SNuD 2019-11-11 23:57:35 +01:00
commit fb41969b89
23 changed files with 394 additions and 302 deletions

View file

@ -97,7 +97,7 @@ namespace ARMeilleure.CodeGen.X86
Add(X86Instruction.Cvtpd2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Cvtps2dq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5b, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Cvtps2pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex));
Add(X86Instruction.Cvtsd2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2c, InstructionFlags.Vex | InstructionFlags.PrefixF2));
Add(X86Instruction.Cvtsd2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF2));
Add(X86Instruction.Cvtsd2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF2));
Add(X86Instruction.Cvtsi2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF2));
Add(X86Instruction.Cvtsi2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF3));

View file

@ -265,7 +265,21 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(dest.Type.IsInteger() && !source.Type.IsInteger());
context.Assembler.WriteInstruction(info.Inst, dest, source, dest.Type);
if (intrinOp.Intrinsic == Intrinsic.X86Cvtsi2si)
{
if (dest.Type == OperandType.I32)
{
context.Assembler.Movd(dest, source); // int _mm_cvtsi128_si32
}
else /* if (dest.Type == OperandType.I64) */
{
context.Assembler.Movq(dest, source); // __int64 _mm_cvtsi128_si64
}
}
else
{
context.Assembler.WriteInstruction(info.Inst, dest, source, dest.Type);
}
break;
}

View file

@ -26,7 +26,7 @@ namespace ARMeilleure.CodeGen.X86
public static bool ForceLegacySse { get; set; }
public static bool SupportsVexEncoding => !ForceLegacySse && SupportsAvx;
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
static HardwareCapabilities()
{

View file

@ -37,6 +37,7 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Cvtps2pd, new IntrinsicInfo(X86Instruction.Cvtps2pd, IntrinsicType.Unary));
Add(Intrinsic.X86Cvtsd2si, new IntrinsicInfo(X86Instruction.Cvtsd2si, IntrinsicType.UnaryToGpr));
Add(Intrinsic.X86Cvtsd2ss, new IntrinsicInfo(X86Instruction.Cvtsd2ss, IntrinsicType.Binary));
Add(Intrinsic.X86Cvtsi2si, new IntrinsicInfo(X86Instruction.Movd, IntrinsicType.UnaryToGpr));
Add(Intrinsic.X86Cvtss2sd, new IntrinsicInfo(X86Instruction.Cvtss2sd, IntrinsicType.Binary));
Add(Intrinsic.X86Divpd, new IntrinsicInfo(X86Instruction.Divpd, IntrinsicType.Binary));
Add(Intrinsic.X86Divps, new IntrinsicInfo(X86Instruction.Divps, IntrinsicType.Binary));

View file

@ -322,7 +322,7 @@ namespace ARMeilleure.Instructions
public static void Fcmge_S(ArmEmitterContext context)
{
if (Optimizations.FastFP && Optimizations.UseSse2)
if (Optimizations.FastFP && Optimizations.UseAvx)
{
EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThanOrEqual, scalar: true);
}
@ -334,7 +334,7 @@ namespace ARMeilleure.Instructions
public static void Fcmge_V(ArmEmitterContext context)
{
if (Optimizations.FastFP && Optimizations.UseSse2)
if (Optimizations.FastFP && Optimizations.UseAvx)
{
EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThanOrEqual, scalar: false);
}
@ -346,7 +346,7 @@ namespace ARMeilleure.Instructions
public static void Fcmgt_S(ArmEmitterContext context)
{
if (Optimizations.FastFP && Optimizations.UseSse2)
if (Optimizations.FastFP && Optimizations.UseAvx)
{
EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThan, scalar: true);
}
@ -358,7 +358,7 @@ namespace ARMeilleure.Instructions
public static void Fcmgt_V(ArmEmitterContext context)
{
if (Optimizations.FastFP && Optimizations.UseSse2)
if (Optimizations.FastFP && Optimizations.UseAvx)
{
EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThan, scalar: false);
}
@ -372,7 +372,7 @@ namespace ARMeilleure.Instructions
{
if (Optimizations.FastFP && Optimizations.UseSse2)
{
EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThanOrEqual, scalar: true, isLeOrLt: true);
EmitCmpSseOrSse2OpF(context, CmpCondition.LessThanOrEqual, scalar: true);
}
else
{
@ -384,7 +384,7 @@ namespace ARMeilleure.Instructions
{
if (Optimizations.FastFP && Optimizations.UseSse2)
{
EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThanOrEqual, scalar: false, isLeOrLt: true);
EmitCmpSseOrSse2OpF(context, CmpCondition.LessThanOrEqual, scalar: false);
}
else
{
@ -396,7 +396,7 @@ namespace ARMeilleure.Instructions
{
if (Optimizations.FastFP && Optimizations.UseSse2)
{
EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThan, scalar: true, isLeOrLt: true);
EmitCmpSseOrSse2OpF(context, CmpCondition.LessThan, scalar: true);
}
else
{
@ -408,7 +408,7 @@ namespace ARMeilleure.Instructions
{
if (Optimizations.FastFP && Optimizations.UseSse2)
{
EmitCmpSseOrSse2OpF(context, CmpCondition.GreaterThan, scalar: false, isLeOrLt: true);
EmitCmpSseOrSse2OpF(context, CmpCondition.LessThan, scalar: false);
}
else
{
@ -426,7 +426,7 @@ namespace ARMeilleure.Instructions
EmitFcmpOrFcmpe(context, signalNaNs: true);
}
public static void EmitFccmpOrFccmpe(ArmEmitterContext context, bool signalNaNs)
private static void EmitFccmpOrFccmpe(ArmEmitterContext context, bool signalNaNs)
{
OpCodeSimdFcond op = (OpCodeSimdFcond)context.CurrOp;
@ -435,7 +435,7 @@ namespace ARMeilleure.Instructions
context.BranchIfTrue(lblTrue, InstEmitFlowHelper.GetCondTrue(context, op.Cond));
EmitSetNzcv(context, Const(op.Nzcv));
EmitSetNzcv(context, op.Nzcv);
context.Branch(lblEnd);
@ -446,27 +446,47 @@ namespace ARMeilleure.Instructions
context.MarkLabel(lblEnd);
}
private static void EmitSetNzcv(ArmEmitterContext context, int nzcv)
{
Operand Extract(int value, int bit)
{
if (bit != 0)
{
value >>= bit;
}
value &= 1;
return Const(value);
}
SetFlag(context, PState.VFlag, Extract(nzcv, 0));
SetFlag(context, PState.CFlag, Extract(nzcv, 1));
SetFlag(context, PState.ZFlag, Extract(nzcv, 2));
SetFlag(context, PState.NFlag, Extract(nzcv, 3));
}
private static void EmitFcmpOrFcmpe(ArmEmitterContext context, bool signalNaNs)
{
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
const int cmpOrdered = 7;
bool cmpWithZero = !(op is OpCodeSimdFcond) ? op.Bit3 : false;
if (Optimizations.FastFP && Optimizations.UseSse2)
if (Optimizations.FastFP && (signalNaNs ? Optimizations.UseAvx : Optimizations.UseSse2))
{
Operand n = GetVec(op.Rn);
Operand m = cmpWithZero ? context.VectorZero() : GetVec(op.Rm);
CmpCondition cmpOrdered = signalNaNs ? CmpCondition.OrderedS : CmpCondition.OrderedQ;
Operand lblNaN = Label();
Operand lblEnd = Label();
if (op.Size == 0)
{
Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpss, n, m, Const(cmpOrdered));
Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpss, n, m, Const((int)cmpOrdered));
Operand isOrdered = context.VectorExtract16(ordMask, 0);
Operand isOrdered = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, ordMask);
context.BranchIfFalse(lblNaN, isOrdered);
@ -481,9 +501,9 @@ namespace ARMeilleure.Instructions
}
else /* if (op.Size == 1) */
{
Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, m, Const(cmpOrdered));
Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, m, Const((int)cmpOrdered));
Operand isOrdered = context.VectorExtract16(ordMask, 0);
Operand isOrdered = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, ordMask);
context.BranchIfFalse(lblNaN, isOrdered);
@ -645,18 +665,7 @@ namespace ARMeilleure.Instructions
context.Copy(GetVec(op.Rd), res);
}
private enum CmpCondition
{
Equal = 0,
GreaterThanOrEqual = 5,
GreaterThan = 6
}
private static void EmitCmpSseOrSse2OpF(
ArmEmitterContext context,
CmpCondition cond,
bool scalar,
bool isLeOrLt = false)
private static void EmitCmpSseOrSse2OpF(ArmEmitterContext context, CmpCondition cond, bool scalar)
{
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
@ -669,9 +678,7 @@ namespace ARMeilleure.Instructions
{
Intrinsic inst = scalar ? Intrinsic.X86Cmpss : Intrinsic.X86Cmpps;
Operand res = isLeOrLt
? context.AddIntrinsic(inst, m, n, Const((int)cond))
: context.AddIntrinsic(inst, n, m, Const((int)cond));
Operand res = context.AddIntrinsic(inst, n, m, Const((int)cond));
if (scalar)
{
@ -688,9 +695,7 @@ namespace ARMeilleure.Instructions
{
Intrinsic inst = scalar ? Intrinsic.X86Cmpsd : Intrinsic.X86Cmppd;
Operand res = isLeOrLt
? context.AddIntrinsic(inst, m, n, Const((int)cond))
: context.AddIntrinsic(inst, n, m, Const((int)cond));
Operand res = context.AddIntrinsic(inst, n, m, Const((int)cond));
if (scalar)
{
@ -701,4 +706,4 @@ namespace ARMeilleure.Instructions
}
}
}
}
}

View file

@ -716,8 +716,7 @@ namespace ARMeilleure.Instructions
Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64);
Debug.Assert((uint)size < 2);
OperandType type = size == 0 ? OperandType.FP32
: OperandType.FP64;
OperandType type = size == 0 ? OperandType.FP32 : OperandType.FP64;
if (signed)
{
@ -813,15 +812,12 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn);
const int cmpGreaterThanOrEqual = 5;
const int cmpOrdered = 7;
// sizeF == ((OpCodeSimdShImm64)op).Size - 2
int sizeF = op.Size & 1;
if (sizeF == 0)
{
Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const(cmpOrdered));
Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ));
Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n);
@ -843,7 +839,7 @@ namespace ARMeilleure.Instructions
Operand mask = X86GetAllElements(context, 0x4F000000); // 2.14748365E9f (2147483648)
Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, nRnd, mask, Const(cmpGreaterThanOrEqual));
Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, nRnd, mask, Const((int)CmpCondition.NotLessThan));
Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, mask2);
@ -860,7 +856,7 @@ namespace ARMeilleure.Instructions
}
else /* if (sizeF == 1) */
{
Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const(cmpOrdered));
Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ));
Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n);
@ -896,7 +892,7 @@ namespace ARMeilleure.Instructions
Operand mask = X86GetAllElements(context, 0x43E0000000000000L); // 9.2233720368547760E18d (9223372036854775808)
Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, nRnd, mask, Const(cmpGreaterThanOrEqual));
Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, nRnd, mask, Const((int)CmpCondition.NotLessThan));
Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, mask2);
@ -915,16 +911,12 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn);
const int cmpGreaterThanOrEqual = 5;
const int cmpGreaterThan = 6;
const int cmpOrdered = 7;
// sizeF == ((OpCodeSimdShImm)op).Size - 2
int sizeF = op.Size & 1;
if (sizeF == 0)
{
Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const(cmpOrdered));
Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ));
Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n);
@ -942,7 +934,7 @@ namespace ARMeilleure.Instructions
Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundps, nScaled, Const(X86GetRoundControl(roundMode)));
Operand nRndMask = context.AddIntrinsic(Intrinsic.X86Cmpps, nRnd, context.VectorZero(), Const(cmpGreaterThan));
Operand nRndMask = context.AddIntrinsic(Intrinsic.X86Cmpps, nRnd, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual));
Operand nRndMasked = context.AddIntrinsic(Intrinsic.X86Pand, nRnd, nRndMask);
@ -952,13 +944,13 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Subps, nRndMasked, mask);
Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, res, context.VectorZero(), Const(cmpGreaterThan));
Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, res, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual));
Operand resMasked = context.AddIntrinsic(Intrinsic.X86Pand, res, mask2);
res = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, resMasked);
Operand mask3 = context.AddIntrinsic(Intrinsic.X86Cmpps, resMasked, mask, Const(cmpGreaterThanOrEqual));
Operand mask3 = context.AddIntrinsic(Intrinsic.X86Cmpps, resMasked, mask, Const((int)CmpCondition.NotLessThan));
res = context.AddIntrinsic(Intrinsic.X86Pxor, res, mask3);
res = context.AddIntrinsic(Intrinsic.X86Paddd, res, nInt);
@ -976,7 +968,7 @@ namespace ARMeilleure.Instructions
}
else /* if (sizeF == 1) */
{
Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const(cmpOrdered));
Operand nMask = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ));
Operand nScaled = context.AddIntrinsic(Intrinsic.X86Pand, nMask, n);
@ -994,7 +986,7 @@ namespace ARMeilleure.Instructions
Operand nRnd = context.AddIntrinsic(Intrinsic.X86Roundpd, nScaled, Const(X86GetRoundControl(roundMode)));
Operand nRndMask = context.AddIntrinsic(Intrinsic.X86Cmppd, nRnd, context.VectorZero(), Const(cmpGreaterThan));
Operand nRndMask = context.AddIntrinsic(Intrinsic.X86Cmppd, nRnd, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual));
Operand nRndMasked = context.AddIntrinsic(Intrinsic.X86Pand, nRnd, nRndMask);
@ -1018,7 +1010,7 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Subpd, nRndMasked, mask);
Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, res, context.VectorZero(), Const(cmpGreaterThan));
Operand mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, res, context.VectorZero(), Const((int)CmpCondition.NotLessThanOrEqual));
Operand resMasked = context.AddIntrinsic(Intrinsic.X86Pand, res, mask2);
@ -1032,7 +1024,7 @@ namespace ARMeilleure.Instructions
res = EmitVectorLongCreate(context, low, high);
Operand mask3 = context.AddIntrinsic(Intrinsic.X86Cmppd, resMasked, mask, Const(cmpGreaterThanOrEqual));
Operand mask3 = context.AddIntrinsic(Intrinsic.X86Cmppd, resMasked, mask, Const((int)CmpCondition.NotLessThan));
res = context.AddIntrinsic(Intrinsic.X86Pxor, res, mask3);
res = context.AddIntrinsic(Intrinsic.X86Paddq, res, nInt);

View file

@ -1065,6 +1065,21 @@ namespace ARMeilleure.Instructions
}
}
public enum CmpCondition
{
// Legacy Sse.
Equal = 0, // Ordered, non-signaling.
LessThan = 1, // Ordered, signaling.
LessThanOrEqual = 2, // Ordered, signaling.
NotLessThan = 5, // Unordered, signaling.
NotLessThanOrEqual = 6, // Unordered, signaling.
OrderedQ = 7, // Non-signaling.
// Vex.
GreaterThanOrEqual = 13, // Ordered, signaling.
GreaterThan = 14, // Ordered, signaling.
OrderedS = 23 // Signaling.
}
[Flags]
public enum SaturatingFlags

View file

@ -26,6 +26,7 @@ namespace ARMeilleure.IntermediateRepresentation
X86Cvtps2pd,
X86Cvtsd2si,
X86Cvtsd2ss,
X86Cvtsi2si,
X86Cvtss2sd,
X86Divpd,
X86Divps,

View file

@ -15,6 +15,7 @@ namespace ARMeilleure
public static bool UseSse41IfAvailable { get; set; } = true;
public static bool UseSse42IfAvailable { get; set; } = true;
public static bool UsePopCntIfAvailable { get; set; } = true;
public static bool UseAvxIfAvailable { get; set; } = true;
public static bool ForceLegacySse
{
@ -29,5 +30,6 @@ namespace ARMeilleure
internal static bool UseSse41 => UseSse41IfAvailable && HardwareCapabilities.SupportsSse41;
internal static bool UseSse42 => UseSse42IfAvailable && HardwareCapabilities.SupportsSse42;
internal static bool UsePopCnt => UsePopCntIfAvailable && HardwareCapabilities.SupportsPopcnt;
internal static bool UseAvx => UseAvxIfAvailable && HardwareCapabilities.SupportsAvx && !ForceLegacySse;
}
}

112
KEYS.md
View file

@ -2,103 +2,39 @@
Keys are required for decrypting most of the file formats used by the Nintendo Switch.
Keysets are stored as text files. These 3 filenames are automatically read:
`prod.keys` - Contains common keys usedy by all Switch devices.
`console.keys` - Contains console-unique keys.
`title.keys` - Contains game-specific keys.
Keysets are stored as text files. These 2 filenames are automatically read:
* `prod.keys` - Contains common keys used by all Nintendo Switch devices.
* `title.keys` - Contains game-specific keys.
Ryujinx will first look for keys in `RyuFS/system`, and if it doesn't find any there it will look in `$HOME/.switch`.
A guide to assist with dumping your own keys can be found [here](https://gist.github.com/roblabla/d8358ab058bbe3b00614740dcba4f208).
## Common keys
Here is a template for a key file containing the main keys Ryujinx uses to read content files.
Both `prod.keys` and `console.keys` use this format.
```
master_key_00 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_03 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_04 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
master_key_05 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
titlekek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
key_area_key_application_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
key_area_key_ocean_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
key_area_key_system_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
aes_kek_generation_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
aes_key_generation_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
header_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
header_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```
To dump your `prod.keys` and `title.keys` please follow these following steps.
1. First off learn how to boot into RCM mode and inject payloads if you haven't already. This can be done [here](https://nh-server.github.io/switch-guide/).
2. Make sure you have an SD card with the latest release of [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere/releases) inserted into your Nintendo Switch.
3. Download the latest release of [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM/releases).
4. Boot into RCM mode.
5. Inject the `Lockpick_RCM.bin` that you have downloaded at `Step 3.` using your preferred payload injector. We recommend [TegraRCMGUI](https://github.com/eliboa/TegraRcmGUI/releases) as it is easy to use and has a decent feature set.
6. Using the `Vol+/-` buttons to navigate and the `Power` button to select, select `Dump from SysNAND | Key generation: X` ("X" depends on your Nintendo Switch's firmware version)
7. The dumping process may take a while depending on how many titles you have installed.
8. After its completion press any button to return to the main menu of Lockpick_RCM.
9. Navigate to and select `Power off` if you have an SD card reader. Or you could Navigate and select `Reboot (RCM)` if you want to mount your SD card using `TegraRCMGUI > Tools > Memloader V3 > MMC - SD Card`.
10. You can find your keys in `sd:/switch/prod.keys` and `sd:/switch/title.keys` respectively.
11. Copy these files and paste them in `RyuFS/system`.
And you're done!
## Title keys
Title keys are stored in the format `rights_id,key`.
These are only used for games that are not dumped from cartridges but from games downloaded from the Nintendo eShop, these are also only used if the eShop dump does *not* have a `ticket`. If the game does have a ticket, Ryujinx will read the key directly from that ticket.
Title keys are stored in the format `rights_id = key`.
For example:
```
01000000000100000000000000000003,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01000000000108000000000000000003,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01000000000108000000000000000004,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01000000000100000000000000000003 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01000000000108000000000000000003 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
01000000000108000000000000000004 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```
## Complete key list
Below is a complete list of keys that are currently recognized.
\## represents a hexadecimal number between 00 and 1F
@@ represents a hexadecimal number between 00 and 03
## Prod keys
### Common keys
```
master_key_source
keyblob_mac_key_source
package2_key_source
aes_kek_generation_source
aes_key_generation_source
key_area_key_application_source
key_area_key_ocean_source
key_area_key_system_source
titlekek_source
header_kek_source
header_key_source
sd_card_kek_source
sd_card_nca_key_source
sd_card_save_key_source
retail_specific_aes_key_source
per_console_key_source
bis_kek_source
bis_key_source_@@
header_key
xci_header_key
eticket_rsa_kek
master_key_##
package1_key_##
package2_key_##
titlekek_##
key_area_key_application_##
key_area_key_ocean_##
key_area_key_system_##
keyblob_key_source_##
keyblob_##
```
### Console-unique keys
```
secure_boot_key
tsec_key
device_key
bis_key_@@
keyblob_key_##
keyblob_mac_key_##
encrypted_keyblob_##
sd_seed
```
These are typically used to decrypt system files and encrypted game files. These keys get changed in about every major system update, so make sure to keep your keys up-to-date if you want to play newer games!

View file

@ -41,7 +41,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
- **Switch Keys**
Everything on the Switch is encrypted, so if you want to run anything other than homebrew, you have to dump encryption keys from your console. To get more information please take a look at our [Keys Documentation](KEYS.md) *(Outdated)*.
Everything on the Switch is encrypted, so if you want to run anything other than homebrew, you have to dump encryption keys from your console. To get more information please take a look at our [Keys Documentation](KEYS.md).
- **FFmpeg Dependencies**
@ -57,7 +57,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
Homebrew is available on many websites, such as the [Switch Appstore](https://www.switchbru.com/appstore/).
A hacked Switch is needed to dump games, which you can learn how to do [here](https://nh-server.github.io/switch-guide/). Once you've hacked your Switch, you need to dump your own games with [NxDumpTool](https://github.com/DarkMatterCore/nxdumptool) to get an XCI dump or [SwitchSDTool](https://github.com/CaitSith2/SwitchSDTool) to get an NSP dump.
A hacked Nintendo Switch is needed to dump games, which you can learn how to do [here](https://nh-server.github.io/switch-guide/). Once you have hacked your Nintendo Switch, you will need to dump your own games with [NxDumpTool](https://github.com/DarkMatterCore/nxdumptool/releases) to get an XCI or NSP dump.
## Features

View file

@ -6,18 +6,5 @@
ErrorCodeShift = 9,
Success = 0,
InvalidMemoryState = (51 << ErrorCodeShift) | ModuleId,
InvalidNro = (52 << ErrorCodeShift) | ModuleId,
InvalidNrr = (53 << ErrorCodeShift) | ModuleId,
MaxNro = (55 << ErrorCodeShift) | ModuleId,
MaxNrr = (56 << ErrorCodeShift) | ModuleId,
NroAlreadyLoaded = (57 << ErrorCodeShift) | ModuleId,
NroHashNotPresent = (54 << ErrorCodeShift) | ModuleId,
UnalignedAddress = (81 << ErrorCodeShift) | ModuleId,
BadSize = (82 << ErrorCodeShift) | ModuleId,
BadNroAddress = (84 << ErrorCodeShift) | ModuleId,
BadNrrAddress = (85 << ErrorCodeShift) | ModuleId,
BadInitialization = (87 << ErrorCodeShift) | ModuleId
}
}

View file

@ -91,7 +91,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
if (isRead && isWrite)
{
if (outputDataPosition < inputDataSize)
if (outputDataSize < inputDataSize)
{
arguments = null;

View file

@ -1,10 +1,49 @@
using System.Runtime.InteropServices;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct ZbcColorArray
{
private uint element0;
private uint element1;
private uint element2;
private uint element3;
public uint this[int index]
{
get
{
if (index == 0)
{
return element0;
}
else if (index == 1)
{
return element1;
}
else if (index == 2)
{
return element2;
}
else if (index == 2)
{
return element3;
}
throw new IndexOutOfRangeException();
}
}
}
[StructLayout(LayoutKind.Sequential)]
struct ZbcSetTableArguments
{
// TODO
public ZbcColorArray ColorDs;
public ZbcColorArray ColorL2;
public uint Depth;
public uint Format;
public uint Type;
}
}

View file

@ -5,19 +5,22 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
namespace Ryujinx.HLE.HOS.Services.Loader
namespace Ryujinx.HLE.HOS.Services.Ro
{
[Service("ldr:ro")]
[Service("ro:1")] // 7.0.0+
class IRoInterface : IpcService
class IRoInterface : IpcService, IDisposable
{
private const int MaxNrr = 0x40;
private const int MaxNro = 0x40;
private const int MaxNrr = 0x40;
private const int MaxNro = 0x40;
private const int MaxMapRetries = 0x200;
private const int GuardPagesSize = 0x4000;
private const uint NrrMagic = 0x3052524E;
private const uint NroMagic = 0x304F524E;
@ -25,12 +28,15 @@ namespace Ryujinx.HLE.HOS.Services.Loader
private List<NrrInfo> _nrrInfos;
private List<NroInfo> _nroInfos;
private bool _isInitialized;
private KProcess _owner;
private static Random _random = new Random();
public IRoInterface(ServiceCtx context)
{
_nrrInfos = new List<NrrInfo>(MaxNrr);
_nroInfos = new List<NroInfo>(MaxNro);
_owner = null;
}
private ResultCode ParseNrr(out NrrInfo nrrInfo, ServiceCtx context, long nrrAddress, long nrrSize)
@ -39,11 +45,11 @@ namespace Ryujinx.HLE.HOS.Services.Loader
if (nrrSize == 0 || nrrAddress + nrrSize <= nrrAddress || (nrrSize & 0xFFF) != 0)
{
return ResultCode.BadSize;
return ResultCode.InvalidSize;
}
else if ((nrrAddress & 0xFFF) != 0)
{
return ResultCode.UnalignedAddress;
return ResultCode.InvalidAddress;
}
StructReader reader = new StructReader(context.Memory, nrrAddress);
@ -55,7 +61,7 @@ namespace Ryujinx.HLE.HOS.Services.Loader
}
else if (header.NrrSize != nrrSize)
{
return ResultCode.BadSize;
return ResultCode.InvalidSize;
}
List<byte[]> hashes = new List<byte[]>();
@ -105,19 +111,19 @@ namespace Ryujinx.HLE.HOS.Services.Loader
if (_nroInfos.Count >= MaxNro)
{
return ResultCode.MaxNro;
return ResultCode.TooManyNro;
}
else if (nroSize == 0 || nroAddress + nroSize <= nroAddress || (nroSize & 0xFFF) != 0)
{
return ResultCode.BadSize;
return ResultCode.InvalidSize;
}
else if (bssSize != 0 && bssAddress + bssSize <= bssAddress)
{
return ResultCode.BadSize;
return ResultCode.InvalidSize;
}
else if ((nroAddress & 0xFFF) != 0)
{
return ResultCode.UnalignedAddress;
return ResultCode.InvalidAddress;
}
uint magic = context.Memory.ReadUInt32((long)nroAddress + 0x10);
@ -140,12 +146,12 @@ namespace Ryujinx.HLE.HOS.Services.Loader
if (!IsNroHashPresent(nroHash))
{
return ResultCode.NroHashNotPresent;
return ResultCode.NotRegistered;
}
if (IsNroLoaded(nroHash))
{
return ResultCode.NroAlreadyLoaded;
return ResultCode.AlreadyLoaded;
}
stream.Position = 0;
@ -187,77 +193,120 @@ namespace Ryujinx.HLE.HOS.Services.Loader
return ResultCode.Success;
}
private ResultCode MapNro(ServiceCtx context, NroInfo info, out ulong nroMappedAddress)
private ResultCode MapNro(KProcess process, NroInfo info, out ulong nroMappedAddress)
{
KMemoryManager memMgr = process.MemoryManager;
int retryCount = 0;
nroMappedAddress = 0;
KMemoryManager memMgr = context.Process.MemoryManager;
ulong targetAddress = memMgr.GetAddrSpaceBaseAddr();
while (true)
while (retryCount++ < MaxMapRetries)
{
if (targetAddress + info.TotalSize >= memMgr.AddrSpaceEnd)
ResultCode result = MapCodeMemoryInProcess(process, info.NroAddress, info.NroSize, out nroMappedAddress);
if (result != ResultCode.Success)
{
return ResultCode.InvalidMemoryState;
return result;
}
KMemoryInfo memInfo = memMgr.QueryMemory(targetAddress);
if (memInfo.State == MemoryState.Unmapped && memInfo.Size >= info.TotalSize)
if (info.BssSize > 0)
{
if (!memMgr.InsideHeapRegion (targetAddress, info.TotalSize) &&
!memMgr.InsideAliasRegion(targetAddress, info.TotalSize))
KernelResult bssMappingResult = memMgr.MapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize);
if (bssMappingResult == KernelResult.InvalidMemState)
{
memMgr.UnmapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize);
memMgr.UnmapProcessCodeMemory(nroMappedAddress, info.NroAddress, info.NroSize);
continue;
}
else if (bssMappingResult != KernelResult.Success)
{
memMgr.UnmapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize);
memMgr.UnmapProcessCodeMemory(nroMappedAddress, info.NroAddress, info.NroSize);
return (ResultCode)bssMappingResult;
}
}
if (CanAddGuardRegionsInProcess(process, nroMappedAddress, info.TotalSize))
{
return ResultCode.Success;
}
}
return ResultCode.InsufficientAddressSpace;
}
private bool CanAddGuardRegionsInProcess(KProcess process, ulong baseAddress, ulong size)
{
KMemoryManager memMgr = process.MemoryManager;
KMemoryInfo memInfo = memMgr.QueryMemory(baseAddress - 1);
if (memInfo.State == MemoryState.Unmapped && baseAddress - GuardPagesSize >= memInfo.Address)
{
memInfo = memMgr.QueryMemory(baseAddress + size);
if (memInfo.State == MemoryState.Unmapped)
{
return baseAddress + size + GuardPagesSize <= memInfo.Address + memInfo.Size;
}
}
return false;
}
private ResultCode MapCodeMemoryInProcess(KProcess process, ulong baseAddress, ulong size, out ulong targetAddress)
{
KMemoryManager memMgr = process.MemoryManager;
targetAddress = 0;
int retryCount;
int addressSpacePageLimit = (int)((memMgr.GetAddrSpaceSize() - size) >> 12);
for (retryCount = 0; retryCount < MaxMapRetries; retryCount++)
{
while (true)
{
targetAddress = memMgr.GetAddrSpaceBaseAddr() + (ulong)(_random.Next(addressSpacePageLimit) << 12);
if (memMgr.InsideAddrSpace(targetAddress, size) && !memMgr.InsideHeapRegion(targetAddress, size) && !memMgr.InsideAliasRegion(targetAddress, size))
{
break;
}
}
targetAddress += memInfo.Size;
}
KernelResult result = memMgr.MapProcessCodeMemory(targetAddress, baseAddress, size);
KernelResult result = memMgr.MapProcessCodeMemory(targetAddress, info.NroAddress, info.NroSize);
if (result != KernelResult.Success)
{
return ResultCode.InvalidMemoryState;
}
ulong bssTargetAddress = targetAddress + info.NroSize;
if (info.BssSize != 0)
{
result = memMgr.MapProcessCodeMemory(bssTargetAddress, info.BssAddress, info.BssSize);
if (result != KernelResult.Success)
if (result == KernelResult.InvalidMemState)
{
memMgr.UnmapProcessCodeMemory(targetAddress, info.NroAddress, info.NroSize);
return ResultCode.InvalidMemoryState;
continue;
}
}
result = LoadNroIntoMemory(context.Process, info.Executable, targetAddress);
if (result != KernelResult.Success)
{
memMgr.UnmapProcessCodeMemory(targetAddress, info.NroAddress, info.NroSize);
if (info.BssSize != 0)
else if (result != KernelResult.Success)
{
memMgr.UnmapProcessCodeMemory(bssTargetAddress, info.BssAddress, info.BssSize);
return (ResultCode)result;
}
if (!CanAddGuardRegionsInProcess(process, targetAddress, size))
{
continue;
}
return ResultCode.Success;
}
info.NroMappedAddress = targetAddress;
nroMappedAddress = targetAddress;
if (retryCount == MaxMapRetries)
{
return ResultCode.InsufficientAddressSpace;
}
return ResultCode.Success;
}
private KernelResult LoadNroIntoMemory(KProcess process, IExecutable relocatableObject, ulong baseAddress)
private KernelResult SetNroMemoryPermissions(KProcess process, IExecutable relocatableObject, ulong baseAddress)
{
ulong textStart = baseAddress + (ulong)relocatableObject.TextOffset;
ulong roStart = baseAddress + (ulong)relocatableObject.RoOffset;
@ -304,10 +353,10 @@ namespace Ryujinx.HLE.HOS.Services.Loader
}
}
return ResultCode.BadNrrAddress;
return ResultCode.NotLoaded;
}
private ResultCode RemoveNroInfo(ServiceCtx context, ulong nroMappedAddress)
private ResultCode RemoveNroInfo(ulong nroMappedAddress)
{
foreach (NroInfo info in _nroInfos)
{
@ -315,49 +364,64 @@ namespace Ryujinx.HLE.HOS.Services.Loader
{
_nroInfos.Remove(info);
ulong textSize = (ulong)info.Executable.Text.Length;
ulong roSize = (ulong)info.Executable.Ro.Length;
ulong dataSize = (ulong)info.Executable.Data.Length;
ulong bssSize = (ulong)info.Executable.BssSize;
KernelResult result = KernelResult.Success;
if (info.Executable.BssSize != 0)
{
result = context.Process.MemoryManager.UnmapProcessCodeMemory(
info.NroMappedAddress + textSize + roSize + dataSize,
info.Executable.BssAddress,
bssSize);
}
if (result == KernelResult.Success)
{
result = context.Process.MemoryManager.UnmapProcessCodeMemory(
info.NroMappedAddress + textSize + roSize,
info.Executable.SourceAddress + textSize + roSize,
dataSize);
if (result == KernelResult.Success)
{
result = context.Process.MemoryManager.UnmapProcessCodeMemory(
info.NroMappedAddress,
info.Executable.SourceAddress,
textSize + roSize);
}
}
return (ResultCode)result;
return UnmapNroFromInfo(info);
}
}
return ResultCode.BadNroAddress;
return ResultCode.NotLoaded;
}
private ResultCode UnmapNroFromInfo(NroInfo info)
{
ulong textSize = (ulong)info.Executable.Text.Length;
ulong roSize = (ulong)info.Executable.Ro.Length;
ulong dataSize = (ulong)info.Executable.Data.Length;
ulong bssSize = (ulong)info.Executable.BssSize;
KernelResult result = KernelResult.Success;
if (info.Executable.BssSize != 0)
{
result = _owner.MemoryManager.UnmapProcessCodeMemory(
info.NroMappedAddress + textSize + roSize + dataSize,
info.Executable.BssAddress,
bssSize);
}
if (result == KernelResult.Success)
{
result = _owner.MemoryManager.UnmapProcessCodeMemory(
info.NroMappedAddress + textSize + roSize,
info.Executable.SourceAddress + textSize + roSize,
dataSize);
if (result == KernelResult.Success)
{
result = _owner.MemoryManager.UnmapProcessCodeMemory(
info.NroMappedAddress,
info.Executable.SourceAddress,
textSize + roSize);
}
}
return (ResultCode)result;
}
private ResultCode IsInitialized(KProcess process)
{
if (_owner != null && _owner.Pid == process.Pid)
{
return ResultCode.Success;
}
return ResultCode.InvalidProcess;
}
[Command(0)]
// LoadNro(u64, u64, u64, u64, u64, pid) -> u64
public ResultCode LoadNro(ServiceCtx context)
{
ResultCode result = ResultCode.BadInitialization;
ResultCode result = IsInitialized(context.Process);
// Zero
context.RequestData.ReadUInt64();
@ -369,19 +433,24 @@ namespace Ryujinx.HLE.HOS.Services.Loader
ulong nroMappedAddress = 0;
if (_isInitialized)
if (result == ResultCode.Success)
{
NroInfo info;
result = ParseNro(out info, context, nroHeapAddress, nroSize, bssHeapAddress, bssSize);
if (result == 0)
if (result == ResultCode.Success)
{
result = MapNro(context, info, out nroMappedAddress);
result = MapNro(context.Process, info, out nroMappedAddress);
if (result == 0)
if (result == ResultCode.Success)
{
_nroInfos.Add(info);
result = (ResultCode)SetNroMemoryPermissions(context.Process, info.Executable, nroMappedAddress);
if (result == ResultCode.Success)
{
_nroInfos.Add(info);
}
}
}
}
@ -395,21 +464,21 @@ namespace Ryujinx.HLE.HOS.Services.Loader
// UnloadNro(u64, u64, pid)
public ResultCode UnloadNro(ServiceCtx context)
{
ResultCode result = ResultCode.BadInitialization;
ResultCode result = IsInitialized(context.Process);
// Zero
context.RequestData.ReadUInt64();
ulong nroMappedAddress = context.RequestData.ReadUInt64();
if (_isInitialized)
if (result == ResultCode.Success)
{
if ((nroMappedAddress & 0xFFF) != 0)
{
return ResultCode.UnalignedAddress;
return ResultCode.InvalidAddress;
}
result = RemoveNroInfo(context, nroMappedAddress);
result = RemoveNroInfo(nroMappedAddress);
}
return result;
@ -419,24 +488,24 @@ namespace Ryujinx.HLE.HOS.Services.Loader
// LoadNrr(u64, u64, u64, pid)
public ResultCode LoadNrr(ServiceCtx context)
{
ResultCode result = ResultCode.BadInitialization;
ResultCode result = IsInitialized(context.Process);
// Zero
// pid placeholder, zero
context.RequestData.ReadUInt64();
long nrrAddress = context.RequestData.ReadInt64();
long nrrSize = context.RequestData.ReadInt64();
if (_isInitialized)
if (result == ResultCode.Success)
{
NrrInfo info;
result = ParseNrr(out info, context, nrrAddress, nrrSize);
if (result == 0)
if (result == ResultCode.Success)
{
if (_nrrInfos.Count >= MaxNrr)
{
result = ResultCode.MaxNrr;
result = ResultCode.NotLoaded;
}
else
{
@ -452,18 +521,18 @@ namespace Ryujinx.HLE.HOS.Services.Loader
// UnloadNrr(u64, u64, pid)
public ResultCode UnloadNrr(ServiceCtx context)
{
ResultCode result = ResultCode.BadInitialization;
ResultCode result = IsInitialized(context.Process);
// Zero
// pid placeholder, zero
context.RequestData.ReadUInt64();
long nrrHeapAddress = context.RequestData.ReadInt64();
if (_isInitialized)
if (result == ResultCode.Success)
{
if ((nrrHeapAddress & 0xFFF) != 0)
{
return ResultCode.UnalignedAddress;
return ResultCode.InvalidAddress;
}
result = RemoveNrrInfo(nrrHeapAddress);
@ -476,10 +545,24 @@ namespace Ryujinx.HLE.HOS.Services.Loader
// Initialize(u64, pid, KObject)
public ResultCode Initialize(ServiceCtx context)
{
// TODO: we actually ignore the pid and process handle receive, we will need to use them when we will have multi process support.
_isInitialized = true;
if (_owner != null)
{
return ResultCode.InvalidSession;
}
_owner = context.Process;
return ResultCode.Success;
}
public void Dispose()
{
foreach (NroInfo info in _nroInfos)
{
UnmapNroFromInfo(info);
}
_nroInfos.Clear();
}
}
}

View file

@ -0,0 +1,27 @@
namespace Ryujinx.HLE.HOS.Services.Ro
{
enum ResultCode
{
ModuleId = 22,
ErrorCodeShift = 22,
Success = 0,
InsufficientAddressSpace = (2 << ErrorCodeShift) | ModuleId,
AlreadyLoaded = (3 << ErrorCodeShift) | ModuleId,
InvalidNro = (4 << ErrorCodeShift) | ModuleId,
InvalidNrr = (6 << ErrorCodeShift) | ModuleId,
TooManyNro = (7 << ErrorCodeShift) | ModuleId,
TooManyNrr = (8 << ErrorCodeShift) | ModuleId,
NotAuthorized = (9 << ErrorCodeShift) | ModuleId,
InvalidNrrType = (10 << ErrorCodeShift) | ModuleId,
InvalidAddress = (1025 << ErrorCodeShift) | ModuleId,
InvalidSize = (1026 << ErrorCodeShift) | ModuleId,
NotLoaded = (1028 << ErrorCodeShift) | ModuleId,
NotRegistered = (1029 << ErrorCodeShift) | ModuleId,
InvalidSession = (1030 << ErrorCodeShift) | ModuleId,
InvalidProcess = (1031 << ErrorCodeShift) | ModuleId,
}
}

View file

@ -1,6 +1,6 @@
using Ryujinx.HLE.Loaders.Executables;
namespace Ryujinx.HLE.HOS.Services.Loader
namespace Ryujinx.HLE.HOS.Services.Ro
{
class NroInfo
{

View file

@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Loader
namespace Ryujinx.HLE.HOS.Services.Ro
{
[StructLayout(LayoutKind.Explicit, Size = 0x350)]
unsafe struct NrrHeader

View file

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Loader
namespace Ryujinx.HLE.HOS.Services.Ro
{
class NrrInfo
{

View file

@ -1,11 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
[StructLayout(LayoutKind.Sequential, Size = 0x8)]
struct Fence
{
public int Id;
public int Value;
}
}

View file

@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using Ryujinx.HLE.HOS.Services.Nv.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
@ -9,15 +10,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
public int FenceCount;
[FieldOffset(0x4)]
public Fence Fence0;
public NvFence Fence0;
[FieldOffset(0xC)]
public Fence Fence1;
public NvFence Fence1;
[FieldOffset(0x14)]
public Fence Fence2;
public NvFence Fence2;
[FieldOffset(0x1C)]
public Fence Fence3;
public NvFence Fence3;
}
}

View file

@ -22,7 +22,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>
</Project>

View file

@ -27,7 +27,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
</ItemGroup>