Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
ac21abbb9d | |||
a3dd04deef | |||
3705c20668 | |||
7b35ebc64a | |||
0a24aa6af2 | |||
c9c65af59e | |||
dc063eac83 | |||
ccf23fc629 | |||
f1460d5494 | |||
644b497df1 | |||
fb935fd201 | |||
f2087ca29e | |||
92d166ecb7 | |||
72e543e946 | |||
98c838b24c | |||
63c9c64196 | |||
a113ed0811 | |||
747876dc67 | |||
95cc18a7b4 | |||
c017c77365 |
@ -18,7 +18,7 @@ namespace ARMeilleure.Decoders
|
||||
// For lower code quality translation, we set a lower limit since we're blocking execution.
|
||||
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>();
|
||||
|
||||
@ -38,7 +38,7 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
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.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);
|
||||
|
||||
opsCount += currBlock.OpCodes.Count;
|
||||
@ -143,7 +149,7 @@ namespace ARMeilleure.Decoders
|
||||
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);
|
||||
}
|
||||
@ -257,6 +263,11 @@ namespace ARMeilleure.Decoders
|
||||
// so we must consider such operations as a branch in potential aswell.
|
||||
if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc)
|
||||
{
|
||||
if (opCode is OpCodeT32)
|
||||
{
|
||||
return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq &&
|
||||
opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
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
|
||||
{
|
||||
interface IOpCode32Alu : IOpCode32
|
||||
interface IOpCode32Alu : IOpCode32, IOpCode32HasSetFlags
|
||||
{
|
||||
int Rd { 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; }
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
class OpCode32AluUmull : OpCode32
|
||||
class OpCode32AluUmull : OpCode32, IOpCode32HasSetFlags
|
||||
{
|
||||
public int RdLo { get; }
|
||||
public int RdHi { 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;
|
||||
}
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using ARMeilleure.Instructions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
@ -972,8 +973,7 @@ namespace ARMeilleure.Decoders
|
||||
SetA32("111100111x11<<10xxxx00011xx0xxxx", InstName.Vzip, InstEmit32.Vzip, OpCode32SimdCmpZ.Create);
|
||||
#endregion
|
||||
|
||||
#region "OpCode Table (AArch32, T16/T32)"
|
||||
// T16
|
||||
#region "OpCode Table (AArch32, T16)"
|
||||
SetT16("000<<xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftImm.Create);
|
||||
SetT16("0001100xxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubReg.Create);
|
||||
SetT16("0001101xxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT16AddSubReg.Create);
|
||||
@ -1045,6 +1045,26 @@ namespace ARMeilleure.Decoders
|
||||
SetT16("11100xxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT16BImm11.Create);
|
||||
#endregion
|
||||
|
||||
#region "OpCode Table (AArch32, T32)"
|
||||
// Base
|
||||
SetT32("11101011010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101011000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101010000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101010001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluRsImm.Create);
|
||||
SetT32("111010110001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("111010111011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101010010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101010011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101010011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101010010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101011110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101011011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluRsImm.Create);
|
||||
SetT32("11101011101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluRsImm.Create);
|
||||
SetT32("111010101001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluRsImm.Create);
|
||||
SetT32("111010100001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluRsImm.Create);
|
||||
#endregion
|
||||
|
||||
FillFastLookupTable(InstA32FastLookup, AllInstA32, ToFastLookupIndexA);
|
||||
FillFastLookupTable(InstT32FastLookup, AllInstT32, ToFastLookupIndexT);
|
||||
FillFastLookupTable(InstA64FastLookup, AllInstA64, ToFastLookupIndexA);
|
||||
@ -1092,8 +1112,11 @@ namespace ARMeilleure.Decoders
|
||||
|
||||
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
|
||||
{
|
||||
encoding = encoding.Substring(16) + encoding.Substring(0, 16);
|
||||
Set(encoding, AllInstT32, new InstDescriptor(name, emitter), makeOp);
|
||||
string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
|
||||
MakeOp reversedMakeOp =
|
||||
(InstDescriptor inst, ulong address, int opCode)
|
||||
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
|
||||
Set(reversedEncoding, AllInstT32, new InstDescriptor(name, emitter), reversedMakeOp);
|
||||
}
|
||||
|
||||
private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
|
||||
|
@ -244,6 +244,23 @@ namespace ARMeilleure.Instructions
|
||||
EmitAluStore(context, res);
|
||||
}
|
||||
|
||||
public static void Orn(ArmEmitterContext context)
|
||||
{
|
||||
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
||||
|
||||
Operand n = GetAluN(context);
|
||||
Operand m = GetAluM(context);
|
||||
|
||||
Operand res = context.BitwiseOr(n, context.BitwiseNot(m));
|
||||
|
||||
if (ShouldSetFlags(context))
|
||||
{
|
||||
EmitNZFlagsCheck(context, res);
|
||||
}
|
||||
|
||||
EmitAluStore(context, res);
|
||||
}
|
||||
|
||||
public static void Pkh(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp;
|
||||
|
@ -14,7 +14,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
public static bool ShouldSetFlags(ArmEmitterContext context)
|
||||
{
|
||||
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
||||
IOpCode32HasSetFlags op = (IOpCode32HasSetFlags)context.CurrOp;
|
||||
|
||||
if (op.SetFlags == null)
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
SetFlag(context, PState.TFlag, bitOne);
|
||||
|
||||
EmitVirtualCall(context, addr);
|
||||
EmitBxWritePc(context, addr);
|
||||
}
|
||||
|
||||
public static void Bx(ArmEmitterContext context)
|
||||
|
@ -12,7 +12,7 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
public static bool IsThumb(OpCode op)
|
||||
{
|
||||
return op is OpCodeT16;
|
||||
return op is OpCodeT16 || op is OpCodeT32;
|
||||
}
|
||||
|
||||
public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type)
|
||||
@ -186,7 +186,7 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -12,7 +12,8 @@ namespace ARMeilleure.Instructions
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -87,7 +88,7 @@ namespace ARMeilleure.Instructions
|
||||
// DC ZVA
|
||||
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));
|
||||
|
||||
@ -98,7 +99,12 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
@ -242,6 +242,11 @@ namespace ARMeilleure.Instructions
|
||||
return (ulong)function.FuncPtr.ToInt64();
|
||||
}
|
||||
|
||||
public static void InvalidateCacheLine(ulong address)
|
||||
{
|
||||
Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes);
|
||||
}
|
||||
|
||||
public static bool CheckSynchronization()
|
||||
{
|
||||
Statistics.PauseTimer();
|
||||
|
@ -114,6 +114,7 @@ namespace ARMeilleure.Translation
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpscr))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)));
|
||||
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.GetTpidr32))); // A32 only.
|
||||
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 InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 3061; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 3138; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
@ -585,7 +585,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
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.");
|
||||
}
|
||||
@ -815,7 +815,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
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.");
|
||||
|
||||
|
@ -96,7 +96,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
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)>();
|
||||
|
||||
|
@ -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.
|
||||
|
@ -49,7 +49,7 @@ namespace ARMeilleure.Translation
|
||||
private readonly AutoResetEvent _backgroundTranslatorEvent;
|
||||
private readonly ReaderWriterLock _backgroundTranslatorLock;
|
||||
|
||||
internal ConcurrentDictionary<ulong, TranslatedFunction> Functions { get; }
|
||||
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
||||
internal AddressTable<ulong> FunctionTable { get; }
|
||||
internal EntryTable<uint> CountTable { get; }
|
||||
internal TranslatorStubs Stubs { get; }
|
||||
@ -75,7 +75,7 @@ namespace ARMeilleure.Translation
|
||||
JitCache.Initialize(allocator);
|
||||
|
||||
CountTable = new EntryTable<uint>();
|
||||
Functions = new ConcurrentDictionary<ulong, TranslatedFunction>();
|
||||
Functions = new TranslatorCache<TranslatedFunction>();
|
||||
FunctionTable = new AddressTable<ulong>(for64Bits ? Levels64Bit : Levels32Bit);
|
||||
Stubs = new TranslatorStubs(this);
|
||||
|
||||
@ -93,12 +93,12 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
_backgroundTranslatorLock.AcquireReaderLock(Timeout.Infinite);
|
||||
|
||||
if (_backgroundStack.TryPop(out RejitRequest request) &&
|
||||
if (_backgroundStack.TryPop(out RejitRequest request) &&
|
||||
_backgroundSet.TryRemove(request.Address, out _))
|
||||
{
|
||||
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);
|
||||
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);
|
||||
|
||||
@ -209,13 +209,24 @@ namespace ARMeilleure.Translation
|
||||
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)
|
||||
{
|
||||
if (!Functions.TryGetValue(address, out TranslatedFunction func))
|
||||
{
|
||||
func = Translate(address, mode, highCq: false);
|
||||
|
||||
TranslatedFunction oldFunc = Functions.GetOrAdd(address, func);
|
||||
TranslatedFunction oldFunc = Functions.GetOrAdd(address, func.GuestSize, 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(
|
||||
Memory,
|
||||
@ -255,7 +266,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
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);
|
||||
|
||||
@ -285,14 +296,14 @@ namespace ARMeilleure.Translation
|
||||
|
||||
var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
|
||||
|
||||
if (context.HasPtc)
|
||||
if (context.HasPtc && !singleStep)
|
||||
{
|
||||
options |= CompilerOptions.Relocatable;
|
||||
}
|
||||
|
||||
CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options);
|
||||
|
||||
if (context.HasPtc)
|
||||
if (context.HasPtc && !singleStep)
|
||||
{
|
||||
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.
|
||||
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)
|
||||
@ -493,7 +521,9 @@ namespace ARMeilleure.Translation
|
||||
// Ensure no attempt will be made to compile new functions due to rejit.
|
||||
ClearRejitQueue(allowRequeue: false);
|
||||
|
||||
foreach (var func in Functions.Values)
|
||||
List<TranslatedFunction> functions = Functions.AsList();
|
||||
|
||||
foreach (var func in functions)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return FdnDelayTimes.AsSpan().Slice((int)lateMode * 4, 4);
|
||||
return FdnDelayTimes.AsSpan((int)lateMode * 4, 4);
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -459,7 +459,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ namespace Ryujinx.Common.Logging
|
||||
ServiceLm,
|
||||
ServiceMii,
|
||||
ServiceMm,
|
||||
ServiceMnpp,
|
||||
ServiceNfc,
|
||||
ServiceNfp,
|
||||
ServiceNgct,
|
||||
|
@ -9,6 +9,7 @@ namespace Ryujinx.Common.Logging
|
||||
Error,
|
||||
Guest,
|
||||
AccessLog,
|
||||
Notice
|
||||
Notice,
|
||||
Trace
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ namespace Ryujinx.Common.Logging
|
||||
public static Log? Guest { get; private set; }
|
||||
public static Log? AccessLog { get; private set; }
|
||||
public static Log? Stub { get; private set; }
|
||||
public static Log? Trace { get; private set; }
|
||||
public static Log Notice { get; } // Always enabled
|
||||
|
||||
static Logger()
|
||||
@ -117,6 +118,7 @@ namespace Ryujinx.Common.Logging
|
||||
Error = new Log(LogLevel.Error);
|
||||
Warning = new Log(LogLevel.Warning);
|
||||
Info = new Log(LogLevel.Info);
|
||||
Trace = new Log(LogLevel.Trace);
|
||||
}
|
||||
|
||||
public static void RestartTime()
|
||||
@ -172,7 +174,7 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
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);
|
||||
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.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog): 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");
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ namespace Ryujinx.Common.Logging
|
||||
LogLevel.Error => ConsoleColor.Red,
|
||||
LogLevel.Stub => ConsoleColor.DarkGray,
|
||||
LogLevel.Notice => ConsoleColor.Cyan,
|
||||
LogLevel.Trace => ConsoleColor.DarkCyan,
|
||||
_ => ConsoleColor.Gray,
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
using System.Reflection;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
// DO NOT EDIT, filled by CI
|
||||
public static class ReleaseInformations
|
||||
{
|
||||
private const string FlatHubChannelOwner = "flathub";
|
||||
|
||||
public static string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
public static string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
public static string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
@ -19,6 +23,11 @@ namespace Ryujinx.Common
|
||||
!ReleaseChannelRepo.StartsWith("%%");
|
||||
}
|
||||
|
||||
public static bool IsFlatHubBuild()
|
||||
{
|
||||
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
}
|
||||
|
||||
public static string GetVersion()
|
||||
{
|
||||
if (IsValid())
|
||||
@ -30,5 +39,15 @@ namespace Ryujinx.Common
|
||||
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
if (IsFlatHubBuild())
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
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)
|
||||
{
|
||||
return (uint)AlignUp((int)value, size);
|
||||
@ -76,60 +75,7 @@ namespace Ryujinx.Common
|
||||
|
||||
public static int Pow2RoundDown(int value)
|
||||
{
|
||||
return IsPowerOfTwo32(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;
|
||||
return BitOperations.IsPow2(value) ? value : Pow2RoundUp(value) >> 1;
|
||||
}
|
||||
|
||||
public static long ReverseBits64(long value)
|
||||
|
@ -28,5 +28,10 @@ namespace Ryujinx.Cpu
|
||||
{
|
||||
_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.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
{
|
||||
@ -44,6 +48,180 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
/// <param name="data">Data to be written</param>
|
||||
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>
|
||||
/// Performs the blit operation, triggered by the register write.
|
||||
/// </summary>
|
||||
@ -114,16 +292,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
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(
|
||||
memoryManager,
|
||||
srcCopyTexture,
|
||||
offset,
|
||||
srcCopyTextureFormat,
|
||||
!canDirectCopy,
|
||||
false,
|
||||
srcHint);
|
||||
|
||||
if (srcTexture == null)
|
||||
{
|
||||
if (canDirectCopy)
|
||||
{
|
||||
// Directly copy the data on CPU.
|
||||
UnscaledFullCopy(srcCopyTexture, dstCopyTexture, srcX2, srcY2, srcCopyTextureFormat.BytesPerPixel);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -132,7 +325,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
// When the source texture that was found 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.
|
||||
FormatInfo dstCopyTextureFormat;
|
||||
|
||||
if (srcTexture.Format.IsDepthOrStencil())
|
||||
{
|
||||
@ -148,6 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
dstCopyTexture,
|
||||
0,
|
||||
dstCopyTextureFormat,
|
||||
true,
|
||||
srcTexture.ScaleMode == TextureScaleMode.Scaled,
|
||||
dstHint);
|
||||
|
||||
|
@ -28,6 +28,14 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
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>
|
||||
/// Enables or disables the Just-in-Time compiler for GPU Macro code.
|
||||
/// </summary>
|
||||
|
@ -40,6 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private readonly PhysicalMemory _physicalMemory;
|
||||
|
||||
private readonly MultiRangeList<Texture> _textures;
|
||||
private readonly HashSet<Texture> _partiallyMappedTextures;
|
||||
|
||||
private Texture[] _textureOverlaps;
|
||||
private OverlapInfo[] _overlapInfo;
|
||||
@ -57,6 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_physicalMemory = physicalMemory;
|
||||
|
||||
_textures = new MultiRangeList<Texture>();
|
||||
_partiallyMappedTextures = new HashSet<Texture>();
|
||||
|
||||
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
|
||||
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
|
||||
@ -74,17 +76,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Texture[] overlaps = new Texture[10];
|
||||
int overlapCount;
|
||||
|
||||
MultiRange unmapped;
|
||||
|
||||
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;
|
||||
}
|
||||
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
||||
|
||||
lock (_textures)
|
||||
{
|
||||
@ -95,6 +87,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
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>
|
||||
@ -194,6 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TwodTexture copyTexture,
|
||||
ulong offset,
|
||||
FormatInfo formatInfo,
|
||||
bool shouldCreate,
|
||||
bool preferScaling = true,
|
||||
Size? sizeHint = null)
|
||||
{
|
||||
@ -234,6 +245,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
flags |= TextureSearchFlags.WithUpscale;
|
||||
}
|
||||
|
||||
if (!shouldCreate)
|
||||
{
|
||||
flags |= TextureSearchFlags.NoCreate;
|
||||
}
|
||||
|
||||
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint);
|
||||
|
||||
texture?.SynchronizeMemory();
|
||||
@ -480,15 +496,29 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
return texture;
|
||||
}
|
||||
else if (flags.HasFlag(TextureSearchFlags.NoCreate))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate texture sizes, used to find all overlapping textures.
|
||||
SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize);
|
||||
|
||||
ulong size = (ulong)sizeInfo.TotalSize;
|
||||
bool partiallyMapped = false;
|
||||
|
||||
if (range == null)
|
||||
{
|
||||
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.
|
||||
@ -658,7 +688,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
else
|
||||
{
|
||||
bool dataOverlaps = texture.DataOverlaps(overlap, compatibility);
|
||||
|
||||
|
||||
if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group))
|
||||
{
|
||||
incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility));
|
||||
@ -774,6 +804,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_textures.Add(texture);
|
||||
}
|
||||
|
||||
if (partiallyMapped)
|
||||
{
|
||||
lock (_partiallyMappedTextures)
|
||||
{
|
||||
_partiallyMappedTextures.Add(texture);
|
||||
}
|
||||
}
|
||||
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
|
||||
for (int i = 0; i < overlapsCount; i++)
|
||||
@ -1069,6 +1107,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
_textures.Remove(texture);
|
||||
}
|
||||
|
||||
lock (_partiallyMappedTextures)
|
||||
{
|
||||
_partiallyMappedTextures.Remove(texture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -236,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <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:
|
||||
// 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))
|
||||
{
|
||||
@ -618,7 +618,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <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 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)));
|
||||
}
|
||||
@ -1097,11 +1097,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
// Single dirty region.
|
||||
var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count];
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < TextureRange.Count; 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);
|
||||
@ -1277,7 +1286,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
TextureInfo info = 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))
|
||||
{
|
||||
// These textures are copy compatible. Create the dependency.
|
||||
|
@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Strict = 1 << 0,
|
||||
ForSampler = 1 << 1,
|
||||
ForCopy = 1 << 2,
|
||||
WithUpscale = 1 << 3
|
||||
WithUpscale = 1 << 3,
|
||||
NoCreate = 1 << 4
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private const int PtLvl1Bit = PtPageBits;
|
||||
private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits;
|
||||
|
||||
public const ulong PteUnmapped = 0xffffffff_ffffffff;
|
||||
public const ulong PteUnmapped = ulong.MaxValue;
|
||||
|
||||
private readonly ulong[][] _pageTable;
|
||||
|
||||
@ -154,14 +154,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Gets a writable region from GPU mapped memory.
|
||||
/// </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="tracked">True if write tracking is triggered on the span</param>
|
||||
/// <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))
|
||||
{
|
||||
return Physical.GetWritableRegion(Translate(va), size);
|
||||
return Physical.GetWritableRegion(Translate(va), size, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -169,7 +170,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
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="size">Size of the range</param>
|
||||
/// <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)
|
||||
{
|
||||
if (IsContiguous(va, (int)size))
|
||||
@ -347,11 +347,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
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 regionSize = Math.Min(size, PageSize - (va & PageMask));
|
||||
|
||||
@ -366,14 +361,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
if (!IsMapped(va + PageSize))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"The specified GPU virtual memory range 0x{va:X}..0x{(va + size):X} is not fully mapped.");
|
||||
}
|
||||
|
||||
ulong currPa = Translate(va);
|
||||
ulong newPa = Translate(va + PageSize);
|
||||
|
||||
if (Translate(va) + PageSize != newPa)
|
||||
if ((currPa != PteUnmapped || newPa != PteUnmapped) && currPa + PageSize != newPa)
|
||||
{
|
||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
||||
regionStart = newPa;
|
||||
@ -404,18 +395,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
MemoryRange currentRange = range.GetSubRange(i);
|
||||
|
||||
ulong address = currentRange.Address & ~PageMask;
|
||||
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
|
||||
|
||||
while (address < endAddress)
|
||||
if (currentRange.Address != PteUnmapped)
|
||||
{
|
||||
if (Translate(va) != address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ulong address = currentRange.Address & ~PageMask;
|
||||
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
|
||||
|
||||
va += PageSize;
|
||||
address += PageSize;
|
||||
while (address < endAddress)
|
||||
{
|
||||
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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
@ -19,8 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
class PhysicalMemory : IDisposable
|
||||
{
|
||||
public const int PageSize = 0x1000;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private IVirtualMemoryManagerTracked _cpuMemory;
|
||||
private int _referenceCount;
|
||||
@ -103,24 +99,28 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (range.Count == 1)
|
||||
{
|
||||
var singleRange = range.GetSubRange(0);
|
||||
return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[range.GetSize()];
|
||||
|
||||
int offset = 0;
|
||||
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
if (singleRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
var currentRange = range.GetSubRange(i);
|
||||
int size = (int)currentRange.Size;
|
||||
_cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size));
|
||||
offset += size;
|
||||
return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked);
|
||||
}
|
||||
|
||||
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>
|
||||
@ -156,11 +156,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
int offset = 0;
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
MemoryRange subrange = range.GetSubRange(i);
|
||||
|
||||
GetSpan(subrange.Address, (int)subrange.Size).CopyTo(memory.Span.Slice(offset, (int)subrange.Size));
|
||||
|
||||
offset += (int)subrange.Size;
|
||||
var currentRange = range.GetSubRange(i);
|
||||
int size = (int)currentRange.Size;
|
||||
if (currentRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
GetSpan(currentRange.Address, size).CopyTo(memory.Span.Slice(offset, size));
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
|
||||
return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memory, tracked);
|
||||
@ -253,7 +255,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
if (range.Count == 1)
|
||||
{
|
||||
var singleRange = range.GetSubRange(0);
|
||||
writeCallback(singleRange.Address, data);
|
||||
if (singleRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
writeCallback(singleRange.Address, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -263,7 +268,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
var currentRange = range.GetSubRange(i);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -288,11 +296,20 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
public GpuRegionHandle BeginTracking(MultiRange range)
|
||||
{
|
||||
var cpuRegionHandles = new CpuRegionHandle[range.Count];
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < range.Count; 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);
|
||||
|
@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
dataSpan[i++] = hash;
|
||||
}
|
||||
|
||||
manifestHeader.UpdateChecksum(data.AsSpan().Slice(Unsafe.SizeOf<CacheManifestHeader>()));
|
||||
manifestHeader.UpdateChecksum(data.AsSpan(Unsafe.SizeOf<CacheManifestHeader>()));
|
||||
|
||||
MemoryMarshal.Write(data, ref manifestHeader);
|
||||
|
||||
@ -447,12 +447,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <summary>
|
||||
/// Version of the codegen (to be changed when codegen or guest format change).
|
||||
/// </summary>
|
||||
private const ulong ShaderCodeGenVersion = 3063;
|
||||
private const ulong ShaderCodeGenVersion = 3132;
|
||||
|
||||
// Progress reporting helpers
|
||||
private volatile int _shaderCount;
|
||||
@ -206,7 +206,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
program = new ShaderProgram(entry.Header.Stage, "");
|
||||
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);
|
||||
|
||||
@ -244,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
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);
|
||||
|
||||
@ -394,8 +394,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
|
||||
// NOTE: Vertex B comes first in the shader cache.
|
||||
byte[] code = entry.Code.AsSpan().Slice(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[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
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);
|
||||
|
||||
|
@ -124,7 +124,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -369,7 +369,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
inst &= Instruction.Mask;
|
||||
bool isImage = inst == Instruction.ImageLoad || 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);
|
||||
|
||||
if (isImage)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using static Ryujinx.Graphics.Texture.BlockLinearConstants;
|
||||
@ -47,15 +48,15 @@ namespace Ryujinx.Graphics.Texture
|
||||
{
|
||||
_texBpp = bpp;
|
||||
|
||||
_bppShift = BitUtils.CountTrailingZeros32(bpp);
|
||||
_bppShift = BitOperations.TrailingZeroCount(bpp);
|
||||
|
||||
_bhMask = gobBlocksInY - 1;
|
||||
_bdMask = gobBlocksInZ - 1;
|
||||
|
||||
_bhShift = BitUtils.CountTrailingZeros32(gobBlocksInY);
|
||||
_bdShift = BitUtils.CountTrailingZeros32(gobBlocksInZ);
|
||||
_bhShift = BitOperations.TrailingZeroCount(gobBlocksInY);
|
||||
_bdShift = BitOperations.TrailingZeroCount(gobBlocksInZ);
|
||||
|
||||
_xShift = BitUtils.CountTrailingZeros32(GobSize * gobBlocksInY * gobBlocksInZ);
|
||||
_xShift = BitOperations.TrailingZeroCount(GobSize * gobBlocksInY * gobBlocksInZ);
|
||||
|
||||
RobAndSliceSizes rsSizes = GetRobAndSliceSizes(width, height, gobBlocksInY, gobBlocksInZ);
|
||||
|
||||
|
@ -481,14 +481,14 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
|
||||
{
|
||||
titleName = controlData.Value.Titles[(int)device.System.State.DesiredTitleLanguage].Name.ToString();
|
||||
titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Value.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
||||
titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
displayVersion = controlData.Value.DisplayVersion.ToString();
|
||||
displayVersion = controlData.Value.DisplayVersionString.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -615,20 +615,20 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
ref ApplicationControlProperty nacp = ref ControlData.Value;
|
||||
|
||||
programInfo.Name = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
|
||||
programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(programInfo.Name))
|
||||
{
|
||||
programInfo.Name = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
||||
programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
if (nacp.PresenceGroupId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.PresenceGroupId;
|
||||
}
|
||||
else if (nacp.SaveDataOwnerId.Value != 0)
|
||||
else if (nacp.SaveDataOwnerId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.SaveDataOwnerId.Value;
|
||||
programInfo.ProgramId = nacp.SaveDataOwnerId;
|
||||
}
|
||||
else if (nacp.AddOnContentBaseId != 0)
|
||||
{
|
||||
@ -776,14 +776,14 @@ namespace Ryujinx.HLE.HOS
|
||||
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
|
||||
control.UserAccountSaveDataSize = 0x4000;
|
||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
||||
control.SaveDataOwnerId = applicationId;
|
||||
control.SaveDataOwnerId = applicationId.Value;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||
}
|
||||
|
||||
HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
|
||||
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, ref control);
|
||||
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
@ -792,7 +792,7 @@ namespace Ryujinx.HLE.HOS
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
resultCode = EnsureApplicationSaveData(hos.Fs, out _, applicationId, ref control, ref user);
|
||||
resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
|
@ -36,6 +36,11 @@ namespace Ryujinx.HLE.HOS
|
||||
_cpuContext.Execute(context, codeAddress);
|
||||
}
|
||||
|
||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
_cpuContext.InvalidateCacheRegion(address, size);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_memoryManager is IRefCounted rc)
|
||||
|
@ -76,6 +76,7 @@ namespace Ryujinx.HLE.HOS
|
||||
internal ServerBase BsdServer { get; private set; }
|
||||
internal ServerBase AudRenServer { get; private set; }
|
||||
internal ServerBase AudOutServer { get; private set; }
|
||||
internal ServerBase FsServer { get; private set; }
|
||||
internal ServerBase HidServer { get; private set; }
|
||||
internal ServerBase NvDrvServer { get; private set; }
|
||||
internal ServerBase TimeServer { get; private set; }
|
||||
@ -298,6 +299,7 @@ namespace Ryujinx.HLE.HOS
|
||||
BsdServer = new ServerBase(KernelContext, "BsdServer");
|
||||
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
|
||||
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
|
||||
FsServer = new ServerBase(KernelContext, "FsServer");
|
||||
HidServer = new ServerBase(KernelContext, "HidServer");
|
||||
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
|
||||
TimeServer = new ServerBase(KernelContext, "TimeServer");
|
||||
@ -464,7 +466,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
AudioRendererManager.Dispose();
|
||||
|
||||
LibHacHorizonManager.AmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value);
|
||||
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
|
||||
|
||||
KernelContext.Dispose();
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
{
|
||||
@ -41,10 +42,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
range++;
|
||||
|
||||
// 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.
|
||||
int rangeLog2 = nextRangeLog2 - (BitUtils.IsPowerOfTwo64(range) ? 1 : 0);
|
||||
int rangeLog2 = nextRangeLog2 - (BitOperations.IsPow2(range) ? 1 : 0);
|
||||
|
||||
int parts = rangeLog2 > 32 ? 2 : 1;
|
||||
int bitsPerPart = rangeLog2 / parts;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
@ -259,11 +260,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
if (backwards)
|
||||
{
|
||||
index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
|
||||
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
|
||||
}
|
||||
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)
|
||||
{
|
||||
index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
|
||||
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
|
||||
}
|
||||
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; }
|
||||
|
||||
void Execute(ExecutionContext context, ulong codeAddress);
|
||||
void InvalidateCacheRegion(ulong address, ulong size);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
int mask = _idMasks[index];
|
||||
|
||||
int firstFreeBit = BitUtils.CountLeadingZeros32((mask + 1) & ~mask);
|
||||
int firstFreeBit = BitOperations.LeadingZeroCount((uint)((mask + 1) & ~mask));
|
||||
|
||||
if (firstFreeBit < 32)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
@ -130,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
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.
|
||||
if (((mask0 & codeMask) & 0x1e008) != 0)
|
||||
|
@ -18,6 +18,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
@ -459,11 +459,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{
|
||||
if (argValues != null)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.KernelSvc, string.Format(formatOrSvcName, argValues));
|
||||
Logger.Trace?.Print(LogClass.KernelSvc, string.Format(formatOrSvcName, argValues));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.KernelSvc, formatOrSvcName);
|
||||
Logger.Trace?.Print(LogClass.KernelSvc, formatOrSvcName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -479,7 +479,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.KernelSvc, $"{svcName} returned result {result}.");
|
||||
Logger.Trace?.Print(LogClass.KernelSvc, $"{svcName} returned result {result}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ namespace Ryujinx.HLE.HOS
|
||||
public HorizonClient BcatClient { get; private set; }
|
||||
public HorizonClient FsClient { get; private set; }
|
||||
public HorizonClient NsClient { get; private set; }
|
||||
public HorizonClient PmClient { get; private set; }
|
||||
public HorizonClient SdbClient { get; private set; }
|
||||
|
||||
private SharedRef<LibHacIReader> _arpIReader;
|
||||
@ -65,6 +66,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
public void InitializeSystemClients()
|
||||
{
|
||||
PmClient = Server.CreatePrivilegedHorizonClient();
|
||||
AccountClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Account, StorageId.BuiltInSystem), AccountFsPermissions);
|
||||
AmClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Am, StorageId.BuiltInSystem), AmFsPermissions);
|
||||
NsClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Ns, StorageId.BuiltInSystem), NsFsPermissions);
|
||||
|
@ -695,7 +695,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
var buildIds = programs.Select(p => p switch
|
||||
{
|
||||
NsoExecutable nso => BitConverter.ToString(nso.BuildId.Bytes.ToArray()).Replace("-", "").TrimEnd('0'),
|
||||
NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()).Replace("-", "").TrimEnd('0'),
|
||||
NroExecutable nro => BitConverter.ToString(nro.Header.BuildId).Replace("-", "").TrimEnd('0'),
|
||||
_ => string.Empty
|
||||
}).ToList();
|
||||
|
@ -160,7 +160,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
var buildIds = executables.Select(e => (e switch
|
||||
{
|
||||
NsoExecutable nso => BitConverter.ToString(nso.BuildId.Bytes.ToArray()),
|
||||
NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()),
|
||||
NroExecutable nro => BitConverter.ToString(nro.Header.BuildId),
|
||||
_ => ""
|
||||
}).Replace("-", "").ToUpper());
|
||||
|
@ -3,6 +3,7 @@ using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@ -25,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
|
||||
public UserProfile LastOpenedUser { get; private set; }
|
||||
|
||||
public AccountManager(HorizonClient horizonClient)
|
||||
public AccountManager(HorizonClient horizonClient, string initialProfileName = null)
|
||||
{
|
||||
_horizonClient = horizonClient;
|
||||
|
||||
@ -43,7 +44,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenUser(_accountSaveDataManager.LastOpened);
|
||||
UserId commandLineUserProfileOverride = default;
|
||||
if (!string.IsNullOrEmpty(initialProfileName))
|
||||
{
|
||||
commandLineUserProfileOverride = _profiles.Values.FirstOrDefault(x => x.Name == initialProfileName)?.UserId ?? default;
|
||||
if (commandLineUserProfileOverride.IsNull)
|
||||
Logger.Warning?.Print(LogClass.Application, $"The command line specified profile named '{initialProfileName}' was not found");
|
||||
}
|
||||
OpenUser(commandLineUserProfileOverride.IsNull ? _accountSaveDataManager.LastOpened : commandLineUserProfileOverride);
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,8 +176,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
|
||||
private void DeleteSaveData(UserId userId)
|
||||
{
|
||||
SaveDataFilter saveDataFilter = new SaveDataFilter();
|
||||
saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low));
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: default,
|
||||
new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low), saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
|
@ -169,7 +169,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
|
||||
// But since we use LibHac and we load one Application at a time, it's not necessary.
|
||||
|
||||
context.ResponseData.Write(context.Device.Application.ControlData.Value.UserAccountSwitchLock);
|
||||
context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
|
||||
|
||||
|
@ -15,12 +15,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
// GetAppletResourceUserId() -> nn::applet::AppletResourceUserId
|
||||
public ResultCode GetAppletResourceUserId(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
long appletResourceUserId = context.Device.System.AppletState.AppletResourceUserIds.Add(_pid);
|
||||
|
||||
context.ResponseData.Write(appletResourceUserId);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { appletResourceUserId });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
}
|
||||
|
||||
HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
|
||||
Result result = EnsureApplicationSaveData(hos.Fs, out long requiredSize, applicationId, ref control, ref userId);
|
||||
Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId);
|
||||
|
||||
context.ResponseData.Write(requiredSize);
|
||||
|
||||
@ -148,7 +148,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
// TODO: When above calls are implemented, switch to using ns:am
|
||||
|
||||
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
|
||||
int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguages;
|
||||
int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag;
|
||||
int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
|
||||
|
||||
if (firstSupported > (int)SystemState.TitleLanguage.BrazilianPortuguese)
|
||||
@ -190,7 +190,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
// GetDisplayVersion() -> nn::oe::DisplayVersion
|
||||
public ResultCode GetDisplayVersion(ServiceCtx context)
|
||||
{
|
||||
// This should work as DisplayVersion U8Span always gives a 0x10 size byte array.
|
||||
// If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
|
||||
context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion);
|
||||
|
||||
@ -252,7 +251,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
||||
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
|
||||
|
||||
Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
|
||||
out CacheStorageTargetMedia storageTarget, applicationId, ref controlHolder.Value, index, saveSize,
|
||||
out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize,
|
||||
journalSize);
|
||||
|
||||
if (result.IsFailure()) return (ResultCode)result.Value;
|
||||
|
@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
launchProperty = new LibHac.Arp.ApplicationLaunchProperty
|
||||
{
|
||||
BaseStorageId = StorageId.BuiltInUser,
|
||||
StorageId = StorageId.BuiltInUser,
|
||||
ApplicationId = ApplicationId
|
||||
};
|
||||
|
||||
@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
launchProperty = new LibHac.Arp.ApplicationLaunchProperty
|
||||
{
|
||||
BaseStorageId = StorageId.BuiltInUser,
|
||||
StorageId = StorageId.BuiltInUser,
|
||||
ApplicationId = applicationId
|
||||
};
|
||||
|
||||
|
@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
private int _disposeState;
|
||||
|
||||
public DisposableIpcService(ServerBase server = null) : base(server) { }
|
||||
|
||||
protected abstract void Dispose(bool isDisposing);
|
||||
|
||||
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)
|
||||
{
|
||||
ulong position = (ulong)context.Request.PtrBuff[index].Position;
|
||||
ulong size = (ulong)context.Request.PtrBuff[index].Size;
|
||||
ulong position = context.Request.PtrBuff[index].Position;
|
||||
ulong size = context.Request.PtrBuff[index].Size;
|
||||
|
||||
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
|
||||
ReadOnlySpan<FspPath> fspBuffer = MemoryMarshal.Cast<byte, FspPath>(buffer);
|
||||
@ -157,8 +145,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
|
||||
public static ref readonly LibHac.FsSrv.Sf.Path GetSfPath(ServiceCtx context, int index = 0)
|
||||
{
|
||||
ulong position = (ulong)context.Request.PtrBuff[index].Position;
|
||||
ulong size = (ulong)context.Request.PtrBuff[index].Size;
|
||||
ulong position = context.Request.PtrBuff[index].Position;
|
||||
ulong size = context.Request.PtrBuff[index].Size;
|
||||
|
||||
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
|
||||
ReadOnlySpan<LibHac.FsSrv.Sf.Path> pathBuffer = MemoryMarshal.Cast<byte, LibHac.FsSrv.Sf.Path>(buffer);
|
||||
|
@ -1,6 +1,6 @@
|
||||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsSrv;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
{
|
||||
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;
|
||||
_baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject();
|
||||
|
@ -24,6 +24,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
private bool _vibrationPermitted;
|
||||
private bool _usbFullKeyControllerEnabled;
|
||||
private bool _isFirmwareUpdateAvailableForSixAxisSensor;
|
||||
private bool _isSixAxisSensorUnalteredPassthroughEnabled;
|
||||
|
||||
private HidNpadJoyAssignmentMode _npadJoyAssignmentMode;
|
||||
private HidNpadHandheldActivationMode _npadHandheldActivationMode;
|
||||
@ -335,7 +336,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// StartSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||
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();
|
||||
|
||||
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)
|
||||
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();
|
||||
|
||||
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
|
||||
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();
|
||||
|
||||
context.ResponseData.Write(_sixAxisSensorFusionEnabled);
|
||||
@ -373,9 +377,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// EnableSixAxisSensorFusion(bool Enabled, nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||
public ResultCode EnableSixAxisSensorFusion(ServiceCtx context)
|
||||
{
|
||||
_sixAxisSensorFusionEnabled = context.RequestData.ReadBoolean();
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
_sixAxisSensorFusionEnabled = context.RequestData.ReadUInt32() != 0;
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
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)
|
||||
public ResultCode SetSixAxisSensorFusionParameters(ServiceCtx context)
|
||||
{
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
|
||||
_sensorFusionParams = new HidSensorFusionParameters
|
||||
{
|
||||
@ -405,7 +410,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// GetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float RevisePower, float ReviseRange)
|
||||
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();
|
||||
|
||||
context.ResponseData.Write(_sensorFusionParams.RevisePower);
|
||||
@ -420,7 +426,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// ResetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||
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();
|
||||
|
||||
_sensorFusionParams.RevisePower = 0;
|
||||
@ -436,6 +443,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
public ResultCode SetAccelerometerParameters(ServiceCtx context)
|
||||
{
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
|
||||
_accelerometerParams = new HidAccelerometerParameters
|
||||
{
|
||||
@ -454,7 +462,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// GetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float X, float Y
|
||||
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();
|
||||
|
||||
context.ResponseData.Write(_accelerometerParams.X);
|
||||
@ -469,7 +478,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// ResetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||
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();
|
||||
|
||||
_accelerometerParams.X = 0;
|
||||
@ -484,9 +494,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// SetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, uint PlayMode, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SetAccelerometerPlayMode(ServiceCtx context)
|
||||
{
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
_accelerometerPlayMode = context.RequestData.ReadUInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
_accelerometerPlayMode = context.RequestData.ReadUInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
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
|
||||
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();
|
||||
|
||||
context.ResponseData.Write(_accelerometerPlayMode);
|
||||
@ -511,7 +523,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// ResetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||
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();
|
||||
|
||||
_accelerometerPlayMode = 0;
|
||||
@ -525,9 +538,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// SetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, uint GyroscopeZeroDriftMode, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context)
|
||||
{
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
_gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
_gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
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
|
||||
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();
|
||||
|
||||
context.ResponseData.Write((int)_gyroscopeZeroDriftMode);
|
||||
@ -552,7 +566,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// ResetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
|
||||
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();
|
||||
|
||||
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
|
||||
@ -566,7 +581,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
|
||||
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();
|
||||
|
||||
bool isAtRest = true;
|
||||
@ -582,8 +598,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
// IsFirmwareUpdateAvailableForSixAxisSensor(nn::hid::AppletResourceUserId, nn::hid::SixAxisSensorHandle, pid) -> bool UpdateAvailable
|
||||
public ResultCode IsFirmwareUpdateAvailableForSixAxisSensor(ServiceCtx context)
|
||||
{
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4;
|
||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
|
||||
@ -593,6 +609,64 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
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)]
|
||||
// ActivateGesture(nn::applet::AppletResourceUserId, int Unknown0)
|
||||
public ResultCode ActivateGesture(ServiceCtx context)
|
||||
@ -606,16 +680,15 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
}
|
||||
|
||||
[CommandHipc(100)]
|
||||
// SetSupportedNpadStyleSet(nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag)
|
||||
// SetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag)
|
||||
public ResultCode SetSupportedNpadStyleSet(ServiceCtx context)
|
||||
{
|
||||
ulong pid = context.Request.HandleDesc.PId;
|
||||
ControllerType type = (ControllerType)context.RequestData.ReadInt32();
|
||||
context.RequestData.BaseStream.Position += 4; // Padding
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new {
|
||||
appletResourceUserId,
|
||||
type
|
||||
});
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, type });
|
||||
|
||||
context.Device.Hid.Npads.SupportedStyleSets = type;
|
||||
|
||||
@ -623,17 +696,15 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
}
|
||||
|
||||
[CommandHipc(101)]
|
||||
// GetSupportedNpadStyleSet(nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag
|
||||
// GetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag
|
||||
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);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new {
|
||||
appletResourceUserId,
|
||||
context.Device.Hid.Npads.SupportedStyleSets
|
||||
});
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, context.Device.Hid.Npads.SupportedStyleSets });
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
|
||||
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 });
|
||||
}
|
||||
|
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
|
||||
}
|
||||
}
|
@ -77,7 +77,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
|
||||
arguments.Handle = CreateHandleFromMap(new NvMapHandle(size));
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!");
|
||||
Logger.Debug?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!");
|
||||
|
||||
return NvInternalResult.Success;
|
||||
}
|
||||
@ -265,7 +265,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
||||
{
|
||||
DeleteMapWithHandle(pid, handle);
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceNv, $"Deleted map {handle}!");
|
||||
Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Arp;
|
||||
using System;
|
||||
|
||||
using static LibHac.Ns.ApplicationControlProperty;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
||||
{
|
||||
class IParentalControlService : IpcService
|
||||
@ -52,8 +53,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
||||
_titleId = titleId;
|
||||
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
|
||||
_ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
|
||||
_parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControl;
|
||||
_ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32);
|
||||
_parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag;
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
||||
private ResultCode IsStereoVisionPermittedImpl()
|
||||
{
|
||||
/*
|
||||
// TODO: Application Exemptions are readed from file "appExemptions.dat" in the service savedata.
|
||||
// TODO: Application Exemptions are read from file "appExemptions.dat" in the service savedata.
|
||||
// Since we don't support the pctl savedata for now, this can be implemented later.
|
||||
|
||||
if (appExemption)
|
||||
|
@ -15,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
||||
|
||||
internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
|
||||
{
|
||||
ref readonly var controlProperty = ref context.Device.Application.ControlData.Value;
|
||||
|
||||
ulong inputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
@ -31,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
||||
}
|
||||
}
|
||||
|
||||
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Application.ControlData.Value.PlayLogQueryCapability;
|
||||
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)controlProperty.PlayLogQueryCapability;
|
||||
|
||||
List<ulong> titleIds = new List<ulong>();
|
||||
|
||||
@ -45,7 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
||||
// Check if input title ids are in the whitelist.
|
||||
foreach (ulong titleId in titleIds)
|
||||
{
|
||||
if (!context.Device.Application.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
|
||||
if (!controlProperty.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId))
|
||||
{
|
||||
return (ResultCode)Am.ResultCode.ObjectInvalid;
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
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));
|
||||
|
||||
|
@ -8,9 +8,9 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
class KipExecutable : IExecutable
|
||||
{
|
||||
public byte[] Program { get; }
|
||||
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize);
|
||||
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize);
|
||||
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize);
|
||||
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
|
||||
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
|
||||
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
|
||||
|
||||
public uint TextOffset { get; }
|
||||
public uint RoOffset { get; }
|
||||
@ -76,7 +76,7 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
{
|
||||
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();
|
||||
|
||||
|
@ -7,9 +7,9 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
class NroExecutable : Nro, IExecutable
|
||||
{
|
||||
public byte[] Program { get; }
|
||||
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)Header.NroSegments[0].Size);
|
||||
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)Header.NroSegments[1].Size);
|
||||
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)Header.NroSegments[2].Size);
|
||||
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)Header.NroSegments[0].Size);
|
||||
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)Header.NroSegments[1].Size);
|
||||
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)Header.NroSegments[2].Size);
|
||||
|
||||
public uint TextOffset => Header.NroSegments[0].FileOffset;
|
||||
public uint RoOffset => Header.NroSegments[1].FileOffset;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Tools.FsSystem;
|
||||
@ -12,9 +12,9 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
class NsoExecutable : IExecutable
|
||||
{
|
||||
public byte[] Program { get; }
|
||||
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize);
|
||||
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize);
|
||||
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize);
|
||||
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
|
||||
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
|
||||
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
|
||||
|
||||
public uint TextOffset { get; }
|
||||
public uint RoOffset { get; }
|
||||
@ -26,8 +26,8 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
public uint DataSize { get; }
|
||||
public uint BssSize { get; }
|
||||
|
||||
public string Name;
|
||||
public Buffer32 BuildId;
|
||||
public string Name;
|
||||
public Array32<byte> BuildId;
|
||||
|
||||
public NsoExecutable(IStorage inStorage, string name = null)
|
||||
{
|
||||
@ -58,7 +58,7 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||
{
|
||||
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();
|
||||
|
||||
|
@ -85,7 +85,7 @@ namespace Ryujinx.HLE.Loaders.Mods
|
||||
|
||||
Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}");
|
||||
|
||||
patch.AsSpan().Slice(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize));
|
||||
patch.AsSpan(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize));
|
||||
|
||||
count++;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Concentus" Version="1.1.7" />
|
||||
<PackageReference Include="LibHac" Version="0.15.0" />
|
||||
<PackageReference Include="LibHac" Version="0.16.0" />
|
||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
|
@ -135,6 +135,9 @@ namespace Ryujinx.Headless.SDL2
|
||||
[Option("enable-error-logs", Required = false, Default = true, HelpText = "Enables printing error log messages.")]
|
||||
public bool? LoggingEnableError { get; set; }
|
||||
|
||||
[Option("enable-trace-logs", Required = false, Default = false, HelpText = "Enables printing trace log messages.")]
|
||||
public bool? LoggingEnableTrace { get; set; }
|
||||
|
||||
[Option("enable-guest-logs", Required = false, Default = true, HelpText = "Enables printing guest log messages.")]
|
||||
public bool? LoggingEnableGuest { get; set; }
|
||||
|
||||
|
@ -310,7 +310,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
{
|
||||
controllerConfig.RangeLeft = 1.0f;
|
||||
controllerConfig.RangeRight = 1.0f;
|
||||
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
|
||||
}
|
||||
}
|
||||
@ -389,13 +389,14 @@ namespace Ryujinx.Headless.SDL2
|
||||
Logger.SetEnable(LogLevel.Info, (bool)option.LoggingEnableInfo);
|
||||
Logger.SetEnable(LogLevel.Warning, (bool)option.LoggingEnableWarning);
|
||||
Logger.SetEnable(LogLevel.Error, (bool)option.LoggingEnableError);
|
||||
Logger.SetEnable(LogLevel.Trace, (bool)option.LoggingEnableTrace);
|
||||
Logger.SetEnable(LogLevel.Guest, (bool)option.LoggingEnableGuest);
|
||||
Logger.SetEnable(LogLevel.AccessLog, (bool)option.LoggingEnableFsAccessLog);
|
||||
|
||||
if ((bool)option.EnableFileLog)
|
||||
{
|
||||
Logger.AddTarget(new AsyncLogTargetWrapper(
|
||||
new FileLogTarget(AppDomain.CurrentDomain.BaseDirectory, "file"),
|
||||
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
|
||||
1000,
|
||||
AsyncLogTargetOverflowAction.Block
|
||||
));
|
||||
|
@ -50,6 +50,13 @@ namespace Ryujinx.Memory.Range
|
||||
ulong otherAddress = other.Address;
|
||||
ulong otherEndAddress = other.EndAddress;
|
||||
|
||||
// If any of the ranges if invalid (address + size overflows),
|
||||
// then they are never considered to overlap.
|
||||
if (thisEndAddress < thisAddress || otherEndAddress < otherAddress)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,12 @@ namespace Ryujinx.Memory.Range
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
var subrange = range.GetSubRange(i);
|
||||
|
||||
if (IsInvalid(ref subrange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_items.Add(subrange.Address, subrange.EndAddress, item);
|
||||
}
|
||||
|
||||
@ -49,6 +55,12 @@ namespace Ryujinx.Memory.Range
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
var subrange = range.GetSubRange(i);
|
||||
|
||||
if (IsInvalid(ref subrange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
removed += _items.Remove(subrange.Address, item);
|
||||
}
|
||||
|
||||
@ -86,6 +98,12 @@ namespace Ryujinx.Memory.Range
|
||||
for (int i = 0; i < range.Count; i++)
|
||||
{
|
||||
var subrange = range.GetSubRange(i);
|
||||
|
||||
if (IsInvalid(ref subrange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
overlapCount = _items.Get(subrange.Address, subrange.EndAddress, ref output, overlapCount);
|
||||
}
|
||||
|
||||
@ -124,6 +142,17 @@ namespace Ryujinx.Memory.Range
|
||||
return overlapCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given sub-range of memory is invalid.
|
||||
/// Those are used to represent unmapped memory regions (holes in the region mapping).
|
||||
/// </summary>
|
||||
/// <param name="subRange">Memory range to checl</param>
|
||||
/// <returns>True if the memory range is considered invalid, false otherwise</returns>
|
||||
private static bool IsInvalid(ref MemoryRange subRange)
|
||||
{
|
||||
return subRange.Address == ulong.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items on the list starting at the specified memory address.
|
||||
/// </summary>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@ -81,7 +82,7 @@ namespace Ryujinx.SDL2.Common
|
||||
|
||||
SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE);
|
||||
|
||||
string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt");
|
||||
string gamepadDbPath = Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt");
|
||||
|
||||
if (File.Exists(gamepadDbPath))
|
||||
{
|
||||
|
@ -257,6 +257,38 @@ namespace Ryujinx.Tests.Cpu
|
||||
return GetContext();
|
||||
}
|
||||
|
||||
public void RunPrecomputedTestCase(PrecomputedThumbTestCase test)
|
||||
{
|
||||
foreach (ushort instruction in test.Instructions)
|
||||
{
|
||||
ThumbOpcode(instruction);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
GetContext().SetX(i, test.StartRegs[i]);
|
||||
}
|
||||
|
||||
uint startCpsr = test.StartRegs[15];
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
GetContext().SetPstateFlag((PState)i, (startCpsr & (1u << i)) != 0);
|
||||
}
|
||||
|
||||
ExecuteOpcodes(runUnicorn: false);
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
Assert.That(GetContext().GetX(i), Is.EqualTo(test.FinalRegs[i]));
|
||||
}
|
||||
|
||||
uint finalCpsr = test.FinalRegs[15];
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.That(GetContext().GetPstateFlag((PState)i), Is.EqualTo((finalCpsr & (1u << i)) != 0));
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetWorkingMemory(uint offset, byte[] data)
|
||||
{
|
||||
_memory.Write(DataBaseAddress + offset, data);
|
||||
|
510
Ryujinx.Tests/Cpu/CpuTestT32Alu.cs
Normal file
510
Ryujinx.Tests/Cpu/CpuTestT32Alu.cs
Normal file
@ -0,0 +1,510 @@
|
||||
using ARMeilleure.State;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Tests.Cpu
|
||||
{
|
||||
[Category("T32Alu")]
|
||||
public sealed class CpuTestT32Alu : CpuTest32
|
||||
{
|
||||
[Test]
|
||||
public void TestT32AluRsImm([ValueSource(nameof(RsImmTestCases))] PrecomputedThumbTestCase test)
|
||||
{
|
||||
RunPrecomputedTestCase(test);
|
||||
}
|
||||
|
||||
public static readonly PrecomputedThumbTestCase[] RsImmTestCases =
|
||||
{
|
||||
// TST (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea18, 0x4f03, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x15a99211, 0x08a56ba3, 0x3c588032, 0xdac302ae, 0x6b7d5b2d, 0x4fe1d8dd, 0x04a574ba, 0x7873779d, 0x17a565d1, 0x63a4bf95, 0xd62594fb, 0x2b9aa84b, 0x20448ccd, 0x70b2197e, 0x00000000, 0x700001f0 },
|
||||
FinalRegs = new uint[] { 0x15a99211, 0x08a56ba3, 0x3c588032, 0xdac302ae, 0x6b7d5b2d, 0x4fe1d8dd, 0x04a574ba, 0x7873779d, 0x17a565d1, 0x63a4bf95, 0xd62594fb, 0x2b9aa84b, 0x20448ccd, 0x70b2197e, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea11, 0x5f67, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xc9754393, 0xec511f2a, 0xc365b8f1, 0xa024565a, 0x089ae8e2, 0xf0c91f23, 0x290f83f4, 0x48f2f445, 0xd3288f2b, 0x7d7b2e44, 0xe80dd37e, 0xb000697f, 0x95be1027, 0x74702206, 0x00000000, 0x200001f0 },
|
||||
FinalRegs = new uint[] { 0xc9754393, 0xec511f2a, 0xc365b8f1, 0xa024565a, 0x089ae8e2, 0xf0c91f23, 0x290f83f4, 0x48f2f445, 0xd3288f2b, 0x7d7b2e44, 0xe80dd37e, 0xb000697f, 0x95be1027, 0x74702206, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x2fc9, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xe9c49eb7, 0x2ca13a97, 0x3fded5a8, 0x30e203e9, 0x811a9ee5, 0x504f95f2, 0x746794b4, 0xfe92b6d6, 0x7608d3c4, 0xf3c5ea36, 0x6290c8f2, 0x45a4a521, 0x359a615c, 0x25674915, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0xe9c49eb7, 0x2ca13a97, 0x3fded5a8, 0x30e203e9, 0x811a9ee5, 0x504f95f2, 0x746794b4, 0xfe92b6d6, 0x7608d3c4, 0xf3c5ea36, 0x6290c8f2, 0x45a4a521, 0x359a615c, 0x25674915, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea15, 0x0f85, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x3130c8d7, 0x5917d350, 0xdf48eedb, 0x23025883, 0x076175bb, 0x5402cc6c, 0x54a95806, 0x7f59c691, 0x9c3eeebf, 0x4b52b4d1, 0xb4eb9626, 0x21fa7996, 0x0ff0a95a, 0x6beb27fd, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0x3130c8d7, 0x5917d350, 0xdf48eedb, 0x23025883, 0x076175bb, 0x5402cc6c, 0x54a95806, 0x7f59c691, 0x9c3eeebf, 0x4b52b4d1, 0xb4eb9626, 0x21fa7996, 0x0ff0a95a, 0x6beb27fd, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x6feb, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x39889074, 0xbea8978e, 0x0331cc7a, 0x448e3b19, 0x33285e9e, 0xdf295408, 0x8444676e, 0xe6998904, 0x819e4da4, 0xb099272c, 0x101385a7, 0x71728a87, 0x76f95b3a, 0x8d5012e4, 0x00000000, 0xc00001f0 },
|
||||
FinalRegs = new uint[] { 0x39889074, 0xbea8978e, 0x0331cc7a, 0x448e3b19, 0x33285e9e, 0xdf295408, 0x8444676e, 0xe6998904, 0x819e4da4, 0xb099272c, 0x101385a7, 0x71728a87, 0x76f95b3a, 0x8d5012e4, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
// AND (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea18, 0x1f52, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xcbe174f1, 0x44be318c, 0x4a8a1a70, 0x1f3c8883, 0x33b316ee, 0x0591a3c5, 0x0ceff4a5, 0xd74988e2, 0xa5ef1873, 0xbd35a940, 0x52a9f4d8, 0xf8662781, 0xda558ea8, 0x4c7d50bc, 0x00000000, 0x400001f0 },
|
||||
FinalRegs = new uint[] { 0xcbe174f1, 0x44be318c, 0x4a8a1a70, 0x1f3c8883, 0x33b316ee, 0x0591a3c5, 0x0ceff4a5, 0xd74988e2, 0xa5ef1873, 0xbd35a940, 0x52a9f4d8, 0xf8662781, 0xda558ea8, 0x4c7d50bc, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea19, 0x4f6b, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x97b9423c, 0x1c25286b, 0x50f84fef, 0xd917c24e, 0x2a5116af, 0xcc65ba10, 0xf5e9dc41, 0xf9f61d10, 0x9876cfe5, 0xd0fdd4bc, 0x95913be0, 0x844c820f, 0xfdaf9519, 0xf3fb09b6, 0x00000000, 0xb00001f0 },
|
||||
FinalRegs = new uint[] { 0x97b9423c, 0x1c25286b, 0x50f84fef, 0xd917c24e, 0x2a5116af, 0xcc65ba10, 0xf5e9dc41, 0xf9f61d10, 0x9876cfe5, 0xd0fdd4bc, 0x95913be0, 0x844c820f, 0xfdaf9519, 0xf3fb09b6, 0x00000000, 0x900001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x3f52, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x1900757b, 0x6914c62d, 0x5eaa28ed, 0xd927c1f7, 0xfd2052ac, 0x146bcb99, 0x604f9b1d, 0xb395bf46, 0x3723ba84, 0xb909d3ec, 0x3db4365e, 0x42df68cd, 0x5fdc10cb, 0x4955b8be, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0x1900757b, 0x6914c62d, 0x5eaa28ed, 0xd927c1f7, 0xfd2052ac, 0x146bcb99, 0x604f9b1d, 0xb395bf46, 0x3723ba84, 0xb909d3ec, 0x3db4365e, 0x42df68cd, 0x5fdc10cb, 0x4955b8be, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x0f17, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x6134093f, 0x115a1456, 0xa7877f6e, 0x2070e9eb, 0x9ddf4a73, 0x14266482, 0x7f98e557, 0xbaa854e0, 0xa37f89a6, 0x641325de, 0xae2dc79b, 0x5b3f2af2, 0x476476d2, 0xb99cc9fd, 0x00000000, 0xb00001f0 },
|
||||
FinalRegs = new uint[] { 0x6134093f, 0x115a1456, 0xa7877f6e, 0x2070e9eb, 0x9ddf4a73, 0x14266482, 0x7f98e557, 0xbaa854e0, 0xa37f89a6, 0x641325de, 0xae2dc79b, 0x5b3f2af2, 0x476476d2, 0xb99cc9fd, 0x00000000, 0x700001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x5f17, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xbaa2bc1a, 0xee3e86d4, 0x3179d65a, 0x8a63cf55, 0x48ea14f4, 0xf85c5d5b, 0x6af50974, 0xf3ded3e9, 0xdab4d6e6, 0x930c07eb, 0x8084b2dd, 0xf6518695, 0x4a3e0f7a, 0x581bd56a, 0x00000000, 0x300001f0 },
|
||||
FinalRegs = new uint[] { 0xbaa2bc1a, 0xee3e86d4, 0x3179d65a, 0x8a63cf55, 0x48ea14f4, 0xf85c5d5b, 0x6af50974, 0xf3ded3e9, 0xdab4d6e6, 0x930c07eb, 0x8084b2dd, 0xf6518695, 0x4a3e0f7a, 0x581bd56a, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
// BIC (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1c, 0x0fbc, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xfc7b7a8b, 0xc1186d54, 0x0a83eda1, 0x88fed37c, 0x5438e8ea, 0xe0af3690, 0x6dba7b9f, 0xa7395bd6, 0xd43af274, 0xbb46f4c2, 0xb65dbcd5, 0xa6bd08b0, 0xb55971c7, 0x2244572e, 0x00000000, 0x700001f0 },
|
||||
FinalRegs = new uint[] { 0xfc7b7a8b, 0xc1186d54, 0x0a83eda1, 0x88fed37c, 0x5438e8ea, 0xe0af3690, 0x6dba7b9f, 0xa7395bd6, 0xd43af274, 0xbb46f4c2, 0xb65dbcd5, 0xa6bd08b0, 0xb55971c7, 0x2244572e, 0x00000000, 0xb00001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x5fe7, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x75f617c8, 0x12ac7ccd, 0x85e6d881, 0x30967bdd, 0xdf66b387, 0xb3d59ccf, 0xe3c824b4, 0xada7a9e4, 0x225da86f, 0x18e008ac, 0x51854224, 0xf3b43823, 0xde37f151, 0x6764b34a, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0x75f617c8, 0x12ac7ccd, 0x85e6d881, 0x30967bdd, 0xdf66b387, 0xb3d59ccf, 0xe3c824b4, 0xada7a9e4, 0x225da86f, 0x18e008ac, 0x51854224, 0xf3b43823, 0xde37f151, 0x6764b34a, 0x00000000, 0x800001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x1fc3, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xde174255, 0x3968e364, 0xf1efd73b, 0x9a159a4e, 0x2b906c3e, 0xf1dfb847, 0x34e3e8f0, 0x39c33745, 0xc368a812, 0x8f3fe175, 0xe3da055f, 0x7737a5d5, 0x7464344a, 0xdb3ac192, 0x00000000, 0x000001f0 },
|
||||
FinalRegs = new uint[] { 0xde174255, 0x3968e364, 0xf1efd73b, 0x9a159a4e, 0x2b906c3e, 0xf1dfb847, 0x34e3e8f0, 0x39c33745, 0xc368a812, 0x8f3fe175, 0xe3da055f, 0x7737a5d5, 0x7464344a, 0xdb3ac192, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea18, 0x6f66, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x368ef4f3, 0x18583461, 0x94f6e104, 0x21e1c1b0, 0x009c85df, 0xe6bddfb2, 0x118e9dad, 0xcdf92eb5, 0xae18b093, 0xe24a54ab, 0x55d1a1a0, 0x0eed1bad, 0x8b6bce47, 0x20b1fdc2, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0x368ef4f3, 0x18583461, 0x94f6e104, 0x21e1c1b0, 0x009c85df, 0xe6bddfb2, 0x118e9dad, 0xcdf92eb5, 0xae18b093, 0xe24a54ab, 0x55d1a1a0, 0x0eed1bad, 0x8b6bce47, 0x20b1fdc2, 0x00000000, 0x700001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x3fc6, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x6f913e40, 0xd1814933, 0x181eb63c, 0x287a5050, 0xe5925dd9, 0x712ee261, 0xcca2e51d, 0x0e88a1ba, 0xa4c8d4c3, 0x26887e3e, 0x83b8de36, 0xc5a5d439, 0x8d2ace7a, 0x9df36292, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0x6f913e40, 0xd1814933, 0x181eb63c, 0x287a5050, 0xe5925dd9, 0x712ee261, 0xcca2e51d, 0x0e88a1ba, 0xa4c8d4c3, 0x26887e3e, 0x83b8de36, 0xc5a5d439, 0x8d2ace7a, 0x9df36292, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
// MOV (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea16, 0x2fdd, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x89fcb953, 0xafbf8db2, 0xad96137f, 0x7901360c, 0x513b561b, 0x2345a005, 0x0ece889b, 0xc8bb918f, 0x270458ce, 0x73bea675, 0xab735592, 0xf68e00e5, 0x88bf2dc1, 0x98601074, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0x89fcb953, 0xafbf8db2, 0xad96137f, 0x7901360c, 0x513b561b, 0x2345a005, 0x0ece889b, 0xc8bb918f, 0x270458ce, 0x73bea675, 0xab735592, 0xf68e00e5, 0x88bf2dc1, 0x98601074, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea19, 0x6fd6, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x35f5eea1, 0x75fe4252, 0x71165923, 0x13ad82d2, 0x01f69a1c, 0x33ff5351, 0x869c335f, 0x70ce9266, 0xf58868ad, 0x4f58e982, 0x89f7df88, 0xd0ba8d45, 0xf45e6e03, 0x7f653972, 0x00000000, 0x800001f0 },
|
||||
FinalRegs = new uint[] { 0x35f5eea1, 0x75fe4252, 0x71165923, 0x13ad82d2, 0x01f69a1c, 0x33ff5351, 0x869c335f, 0x70ce9266, 0xf58868ad, 0x4f58e982, 0x89f7df88, 0xd0ba8d45, 0xf45e6e03, 0x7f653972, 0x00000000, 0x600001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x6f5d, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x1e83719a, 0x1b6405c5, 0x25d9d1d6, 0x3e5fc7f3, 0xd157d610, 0x271b5c46, 0xb65c2838, 0xe4590643, 0x2f2623d7, 0xf1155f93, 0xfa676221, 0x6fac2a1d, 0xc1fa1d8d, 0x8cfa89e1, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0x1e83719a, 0x1b6405c5, 0x25d9d1d6, 0x3e5fc7f3, 0xd157d610, 0x271b5c46, 0xb65c2838, 0xe4590643, 0x2f2623d7, 0xf1155f93, 0xfa676221, 0x6fac2a1d, 0xc1fa1d8d, 0x8cfa89e1, 0x00000000, 0x500001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1c, 0x2fa2, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x431278f1, 0xd3fffe52, 0xbfb4d877, 0x10af0eeb, 0xd375b791, 0xbd19aa81, 0x45eb7ba3, 0x30e47d42, 0xc274e032, 0x6da10d33, 0xfeda1ba4, 0x3dc6205e, 0xc275197e, 0x6c8b86d1, 0x00000000, 0xb00001f0 },
|
||||
FinalRegs = new uint[] { 0x431278f1, 0xd3fffe52, 0xbfb4d877, 0x10af0eeb, 0xd375b791, 0xbd19aa81, 0x45eb7ba3, 0x30e47d42, 0xc274e032, 0x6da10d33, 0xfeda1ba4, 0x3dc6205e, 0xc275197e, 0x6c8b86d1, 0x00000000, 0x900001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x7f7b, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x31f0830d, 0x69dda3f6, 0x983fc927, 0x0407652a, 0x32ceab65, 0xe76a77fd, 0x8a7dd0a6, 0x4892a02f, 0xeab00585, 0xa78bf230, 0x896dd5a9, 0xe3c44398, 0xc2d743d0, 0x42b03803, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0x31f0830d, 0x69dda3f6, 0x983fc927, 0x0407652a, 0x32ceab65, 0xe76a77fd, 0x8a7dd0a6, 0x4892a02f, 0xeab00585, 0xa78bf230, 0x896dd5a9, 0xe3c44398, 0xc2d743d0, 0x42b03803, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
// ORR (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea10, 0x5f72, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x5834d41e, 0x7092ed2e, 0x8994242e, 0x7fac6d96, 0x4d896829, 0x1a578dec, 0x98649fd8, 0x3b713450, 0xca430792, 0xd68d5176, 0xfe0b5c4f, 0xd9caf416, 0xb0e9d5fa, 0x62c57422, 0x00000000, 0x800001f0 },
|
||||
FinalRegs = new uint[] { 0x5834d41e, 0x7092ed2e, 0x8994242e, 0x7fac6d96, 0x4d896829, 0x1a578dec, 0x98649fd8, 0x3b713450, 0xca430792, 0xd68d5176, 0xfe0b5c4f, 0xd9caf416, 0xb0e9d5fa, 0x62c57422, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x0fb4, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x6842aa84, 0x0711ecb6, 0xebae7374, 0x6ea58edd, 0xa6d2837c, 0xbcc1e0d1, 0xe52c9d6c, 0x7bb5fa1c, 0xa7cd6f8a, 0x4558ddb7, 0x7adb449c, 0x95986dd8, 0x7432562c, 0x80d2595c, 0x00000000, 0x400001f0 },
|
||||
FinalRegs = new uint[] { 0x6842aa84, 0x0711ecb6, 0xebae7374, 0x6ea58edd, 0xa6d2837c, 0xbcc1e0d1, 0xe52c9d6c, 0x7bb5fa1c, 0xa7cd6f8a, 0x4558ddb7, 0x7adb449c, 0x95986dd8, 0x7432562c, 0x80d2595c, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x2f78, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x6be94e4c, 0x0569589b, 0xc7e6e127, 0xe5537aea, 0x323e7a85, 0x895e9a94, 0x2341f9b6, 0x9632a18a, 0xa790766f, 0x53533cf3, 0x83cec3aa, 0xa1d042af, 0xabff7e58, 0x614f9bc0, 0x00000000, 0x400001f0 },
|
||||
FinalRegs = new uint[] { 0x6be94e4c, 0x0569589b, 0xc7e6e127, 0xe5537aea, 0x323e7a85, 0x895e9a94, 0x2341f9b6, 0x9632a18a, 0xa790766f, 0x53533cf3, 0x83cec3aa, 0xa1d042af, 0xabff7e58, 0x614f9bc0, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea12, 0x4fbc, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x2e43ac9a, 0xa6a8d4b6, 0xf5853279, 0xf152f284, 0xce9656e5, 0x07642918, 0xd6e25d4a, 0xdebc7fa6, 0x8c3af5e0, 0x3d00cd4c, 0x7e744bb4, 0x2a4b8015, 0x602ea481, 0xdef7571b, 0x00000000, 0x300001f0 },
|
||||
FinalRegs = new uint[] { 0x2e43ac9a, 0xa6a8d4b6, 0xf5853279, 0xf152f284, 0xce9656e5, 0x07642918, 0xd6e25d4a, 0xdebc7fa6, 0x8c3af5e0, 0x3d00cd4c, 0x7e744bb4, 0x2a4b8015, 0x602ea481, 0xdef7571b, 0x00000000, 0xb00001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x7f4c, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x67be4dae, 0xff0f74a8, 0xd769f9e1, 0xb4a98e0a, 0x2988a7dc, 0xb5726464, 0xb7b3fb27, 0x077e539c, 0x9c817cd4, 0xa8cc3981, 0xbe5a7591, 0xc753850a, 0xb8c612a7, 0x6d913c9b, 0x00000000, 0x900001f0 },
|
||||
FinalRegs = new uint[] { 0x67be4dae, 0xff0f74a8, 0xd769f9e1, 0xb4a98e0a, 0x2988a7dc, 0xb5726464, 0xb7b3fb27, 0x077e539c, 0x9c817cd4, 0xa8cc3981, 0xbe5a7591, 0xc753850a, 0xb8c612a7, 0x6d913c9b, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
// MVN (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea15, 0x0ffb, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x4a0a7b4c, 0x4e58d907, 0x386b8207, 0xcd71b0c4, 0x86dcf525, 0x8ae9dba4, 0xf5d6a418, 0xfac79f2e, 0x44cf918b, 0x5d38193b, 0xc17adeaf, 0xa4ad8a86, 0x69527ece, 0x69b75c61, 0x00000000, 0x900001f0 },
|
||||
FinalRegs = new uint[] { 0x4a0a7b4c, 0x4e58d907, 0x386b8207, 0xcd71b0c4, 0x86dcf525, 0x8ae9dba4, 0xf5d6a418, 0xfac79f2e, 0x44cf918b, 0x5d38193b, 0xc17adeaf, 0xa4ad8a86, 0x69527ece, 0x69b75c61, 0x00000000, 0xb00001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x4f01, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xce60a6df, 0xb1127c3f, 0x410d7b4a, 0xd9cfc917, 0xd1b2fc52, 0x8be1e03c, 0xde9b256d, 0xff989abd, 0x07e3c46a, 0x780e7d7c, 0xd807ce82, 0x5e5c8f2b, 0x09232f6d, 0x00746338, 0x00000000, 0x500001f0 },
|
||||
FinalRegs = new uint[] { 0xce60a6df, 0xb1127c3f, 0x410d7b4a, 0xd9cfc917, 0xd1b2fc52, 0x8be1e03c, 0xde9b256d, 0xff989abd, 0x07e3c46a, 0x780e7d7c, 0xd807ce82, 0x5e5c8f2b, 0x09232f6d, 0x00746338, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea18, 0x2f5e, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x18b1240a, 0xa896f734, 0xcd4a40bc, 0x28346a77, 0xbdf09586, 0x3c74ed70, 0x3e255ea3, 0xe55679b4, 0xcc602510, 0x9cd73bfb, 0xf21a6ddb, 0x263a4338, 0x06beb332, 0x0790ac93, 0x00000000, 0xa00001f0 },
|
||||
FinalRegs = new uint[] { 0x18b1240a, 0xa896f734, 0xcd4a40bc, 0x28346a77, 0xbdf09586, 0x3c74ed70, 0x3e255ea3, 0xe55679b4, 0xcc602510, 0x9cd73bfb, 0xf21a6ddb, 0x263a4338, 0x06beb332, 0x0790ac93, 0x00000000, 0x400001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x7f41, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x0c25f69d, 0xc32dc28a, 0xf5e2fe71, 0xe46af209, 0x2d1b6ac8, 0xccac564c, 0x567cc561, 0x63707d28, 0xeae934c8, 0xab78e6f6, 0x2d78d86d, 0x76471cdc, 0x9b909f76, 0xa2cc099d, 0x00000000, 0x200001f0 },
|
||||
FinalRegs = new uint[] { 0x0c25f69d, 0xc32dc28a, 0xf5e2fe71, 0xe46af209, 0x2d1b6ac8, 0xccac564c, 0x567cc561, 0x63707d28, 0xeae934c8, 0xab78e6f6, 0x2d78d86d, 0x76471cdc, 0x9b909f76, 0xa2cc099d, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea19, 0x6ff6, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x6e79c449, 0xe9449bf7, 0x51f8fcb8, 0x138e0b80, 0x715312f2, 0x601ea894, 0xb01f9369, 0x02738c29, 0xee35545f, 0xb61ae4a2, 0xba412f08, 0x1d349e02, 0x56a0dfc0, 0x68cd5bfe, 0x00000000, 0x500001f0 },
|
||||
FinalRegs = new uint[] { 0x6e79c449, 0xe9449bf7, 0x51f8fcb8, 0x138e0b80, 0x715312f2, 0x601ea894, 0xb01f9369, 0x02738c29, 0xee35545f, 0xb61ae4a2, 0xba412f08, 0x1d349e02, 0x56a0dfc0, 0x68cd5bfe, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
// ORN (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x3fd0, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x77034e34, 0xd0727e58, 0x4748dbf2, 0x2becd551, 0x0a650329, 0x005548fa, 0xcfb963c2, 0x9561c965, 0xf157c850, 0x180a1a6c, 0x0252e103, 0x29d0f25a, 0xbd9bbecd, 0xbfd1347c, 0x00000000, 0x100001f0 },
|
||||
FinalRegs = new uint[] { 0x77034e34, 0xd0727e58, 0x4748dbf2, 0x2becd551, 0x0a650329, 0x005548fa, 0xcfb963c2, 0x9561c965, 0xf157c850, 0x180a1a6c, 0x0252e103, 0x29d0f25a, 0xbd9bbecd, 0xbfd1347c, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea16, 0x4f72, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x71cc7ddf, 0x67d4ce81, 0x60b04501, 0xcc90b805, 0xc5f34081, 0x5e83e9f7, 0xb5a78fa9, 0xc2497a71, 0xb20cdf14, 0x4de9f773, 0xf79525ec, 0x26534abd, 0xcd7b59d1, 0x5cfc9554, 0x00000000, 0x200001f0 },
|
||||
FinalRegs = new uint[] { 0x71cc7ddf, 0x67d4ce81, 0x60b04501, 0xcc90b805, 0xc5f34081, 0x5e83e9f7, 0xb5a78fa9, 0xc2497a71, 0xb20cdf14, 0x4de9f773, 0xf79525ec, 0x26534abd, 0xcd7b59d1, 0x5cfc9554, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1d, 0x4fa7, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x73259d94, 0xc85643db, 0xbf238eb1, 0x51648d99, 0xce2971c9, 0xf9e0e440, 0x90de33c9, 0xcf8ac8e9, 0xda964c21, 0x539eb057, 0x3a681b87, 0x11993d47, 0x05a1358f, 0xa8282529, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0x73259d94, 0xc85643db, 0xbf238eb1, 0x51648d99, 0xce2971c9, 0xf9e0e440, 0x90de33c9, 0xcf8ac8e9, 0xda964c21, 0x539eb057, 0x3a681b87, 0x11993d47, 0x05a1358f, 0xa8282529, 0x00000000, 0xb00001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea12, 0x3fdb, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xd274d46b, 0x8937836f, 0x33b78178, 0xc250b807, 0xd3323d2f, 0x82e03ba2, 0xf93bf1a6, 0xb31e0c74, 0xc9238070, 0x957331d1, 0xfaadd1ee, 0x073d40fb, 0x05b3e8b4, 0x93e5233b, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0xd274d46b, 0x8937836f, 0x33b78178, 0xc250b807, 0xd3323d2f, 0x82e03ba2, 0xf93bf1a6, 0xb31e0c74, 0xc9238070, 0x957331d1, 0xfaadd1ee, 0x073d40fb, 0x05b3e8b4, 0x93e5233b, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea15, 0x5f92, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x24755d61, 0xb65b742d, 0xb46476cf, 0x771a9fcd, 0x465b367f, 0x3daa2a47, 0x6984eeb8, 0x238e3187, 0xa9717261, 0x4592be1d, 0x46d19147, 0x6a6e4dc8, 0x4ddd896f, 0x2f899425, 0x00000000, 0xb00001f0 },
|
||||
FinalRegs = new uint[] { 0x24755d61, 0xb65b742d, 0xb46476cf, 0x771a9fcd, 0x465b367f, 0x3daa2a47, 0x6984eeb8, 0x238e3187, 0xa9717261, 0x4592be1d, 0x46d19147, 0x6a6e4dc8, 0x4ddd896f, 0x2f899425, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
// TEQ (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x2f54, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x36675cac, 0xd9259257, 0x0b8ab9ad, 0xbfef324e, 0xf9623cd6, 0xfc1919ff, 0x616b25f5, 0x2d26a3d3, 0x61eb12c8, 0xbb8d48f0, 0xbfb9c232, 0x10383506, 0x31d10885, 0xf29cb615, 0x00000000, 0x500001f0 },
|
||||
FinalRegs = new uint[] { 0x36675cac, 0xd9259257, 0x0b8ab9ad, 0xbfef324e, 0xf9623cd6, 0xfc1919ff, 0x616b25f5, 0x2d26a3d3, 0x61eb12c8, 0xbb8d48f0, 0xbfb9c232, 0x10383506, 0x31d10885, 0xf29cb615, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea17, 0x1f43, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xf7dce25d, 0xbe296478, 0xe1aee674, 0x0414c126, 0xa258cf11, 0x5347cc5f, 0x6f8ed2c9, 0xed554dbe, 0xd3073560, 0x627dbd64, 0xca8bb3fc, 0x9590e3a9, 0xe4bea6bc, 0x557934a6, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0xf7dce25d, 0xbe296478, 0xe1aee674, 0x0414c126, 0xa258cf11, 0x5347cc5f, 0x6f8ed2c9, 0xed554dbe, 0xd3073560, 0x627dbd64, 0xca8bb3fc, 0x9590e3a9, 0xe4bea6bc, 0x557934a6, 0x00000000, 0x900001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea13, 0x1f5b, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x84ad5535, 0xc1f15e65, 0x5ea0078b, 0x79df457d, 0x1c735fe5, 0x06dcfd95, 0x6db96dae, 0x572f572d, 0xac88a919, 0x56d850a6, 0xd5ce3a30, 0x2be992e8, 0x497a47ce, 0x38a74019, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0x84ad5535, 0xc1f15e65, 0x5ea0078b, 0x79df457d, 0x1c735fe5, 0x06dcfd95, 0x6db96dae, 0x572f572d, 0xac88a919, 0x56d850a6, 0xd5ce3a30, 0x2be992e8, 0x497a47ce, 0x38a74019, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea16, 0x3ff3, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xa85ea277, 0x6028643d, 0xa5a85f15, 0x47f0f2a5, 0x9b6eebdb, 0x9f064bc7, 0xab59939f, 0x1a278260, 0xb9f91cfa, 0xf913c49c, 0x2b5c0052, 0x1bf2d6dc, 0x81da80a4, 0xced90006, 0x00000000, 0x000001f0 },
|
||||
FinalRegs = new uint[] { 0xa85ea277, 0x6028643d, 0xa5a85f15, 0x47f0f2a5, 0x9b6eebdb, 0x9f064bc7, 0xab59939f, 0x1a278260, 0xb9f91cfa, 0xf913c49c, 0x2b5c0052, 0x1bf2d6dc, 0x81da80a4, 0xced90006, 0x00000000, 0xa00001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x3f09, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xdf494128, 0xbc975c16, 0x62c66823, 0x95be3737, 0xa07e8778, 0x6ce80cc7, 0xfea03385, 0x4c5bf35a, 0x5cd0bcdf, 0xc47451ab, 0x3849af70, 0x1329c14a, 0xb1f96f79, 0x321eaf12, 0x00000000, 0x700001f0 },
|
||||
FinalRegs = new uint[] { 0xdf494128, 0xbc975c16, 0x62c66823, 0x95be3737, 0xa07e8778, 0x6ce80cc7, 0xfea03385, 0x4c5bf35a, 0x5cd0bcdf, 0xc47451ab, 0x3849af70, 0x1329c14a, 0xb1f96f79, 0x321eaf12, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
// EOR (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea17, 0x6fc0, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x9c803ce5, 0x38e325f9, 0x4d32aea8, 0x0120f77b, 0x8e34b507, 0xee41aabf, 0x7e6d8a0c, 0x761a3f21, 0x99b57f1d, 0x32a4bbf3, 0x9902c1f4, 0xd5e2dd41, 0xe2a08209, 0x2896ceba, 0x00000000, 0xc00001f0 },
|
||||
FinalRegs = new uint[] { 0x9c803ce5, 0x38e325f9, 0x4d32aea8, 0x0120f77b, 0x8e34b507, 0xee41aabf, 0x7e6d8a0c, 0x761a3f21, 0x99b57f1d, 0x32a4bbf3, 0x9902c1f4, 0xd5e2dd41, 0xe2a08209, 0x2896ceba, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1c, 0x4f58, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xe9c3e9b7, 0x26c9e052, 0x708b6153, 0x72dbdc3c, 0xdd3e922d, 0xd0260aca, 0x38dcf6be, 0x4164575f, 0x5d8e03dc, 0x30bfa694, 0xe72a6609, 0xba632c43, 0x1f768178, 0x6b4f56a6, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0xe9c3e9b7, 0x26c9e052, 0x708b6153, 0x72dbdc3c, 0xdd3e922d, 0xd0260aca, 0x38dcf6be, 0x4164575f, 0x5d8e03dc, 0x30bfa694, 0xe72a6609, 0xba632c43, 0x1f768178, 0x6b4f56a6, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea16, 0x7f31, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x9205c1f5, 0x80dd7e44, 0x8ecd5272, 0x3d8d3691, 0x35d45cca, 0x4b2d9eb3, 0xa1652285, 0x6a1cb7f1, 0x8e08b99f, 0xdf8f0c57, 0x28dd0dfa, 0xf2c0abbd, 0x167a6539, 0x75163a9d, 0x00000000, 0xa00001f0 },
|
||||
FinalRegs = new uint[] { 0x9205c1f5, 0x80dd7e44, 0x8ecd5272, 0x3d8d3691, 0x35d45cca, 0x4b2d9eb3, 0xa1652285, 0x6a1cb7f1, 0x8e08b99f, 0xdf8f0c57, 0x28dd0dfa, 0xf2c0abbd, 0x167a6539, 0x75163a9d, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x4f80, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x96547d6c, 0x7afda83c, 0xb3edea2d, 0x49d05904, 0xb661ef76, 0xf1cce5ff, 0x2986ab1b, 0xcb39b044, 0x88937ad3, 0x962cf736, 0x80d6f109, 0xb73dd0d6, 0xb93f9f60, 0xb93a02c9, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0x96547d6c, 0x7afda83c, 0xb3edea2d, 0x49d05904, 0xb661ef76, 0xf1cce5ff, 0x2986ab1b, 0xcb39b044, 0x88937ad3, 0x962cf736, 0x80d6f109, 0xb73dd0d6, 0xb93f9f60, 0xb93a02c9, 0x00000000, 0xb00001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea12, 0x1f35, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xb1a94a51, 0xa8679784, 0xd7558ceb, 0x58d63d95, 0x6e5cf7eb, 0x0d40398d, 0xf88fa339, 0xbe88a56f, 0x7180f980, 0x0795ba21, 0x0732b252, 0xa51be7c8, 0x47c02749, 0xb0fbbd9f, 0x00000000, 0x800001f0 },
|
||||
FinalRegs = new uint[] { 0xb1a94a51, 0xa8679784, 0xd7558ceb, 0x58d63d95, 0x6e5cf7eb, 0x0d40398d, 0xf88fa339, 0xbe88a56f, 0x7180f980, 0x0795ba21, 0x0732b252, 0xa51be7c8, 0x47c02749, 0xb0fbbd9f, 0x00000000, 0xa00001d0 },
|
||||
},
|
||||
// CMN (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x4fc5, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xf4440521, 0x26b151d9, 0x90053d26, 0x8c3bbde1, 0x4a757fa1, 0x34b63513, 0xd1d1a182, 0xa9123bc1, 0xadfbf652, 0xec28d3e6, 0x6ca54af1, 0x385d5637, 0x46280bac, 0x18f38d39, 0x00000000, 0x400001f0 },
|
||||
FinalRegs = new uint[] { 0xf4440521, 0x26b151d9, 0x90053d26, 0x8c3bbde1, 0x4a757fa1, 0x34b63513, 0xd1d1a182, 0xa9123bc1, 0xadfbf652, 0xec28d3e6, 0x6ca54af1, 0x385d5637, 0x46280bac, 0x18f38d39, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x4fe4, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x20abeba4, 0x77f9bd90, 0x5c09b098, 0xdebadd51, 0x6e114dcf, 0xd8212cf8, 0x2a6a57d2, 0xb9667ed8, 0x93817fef, 0x639cd8b4, 0x52b67bce, 0x681ee61c, 0x50bb5414, 0x9f297765, 0x00000000, 0x200001f0 },
|
||||
FinalRegs = new uint[] { 0x20abeba4, 0x77f9bd90, 0x5c09b098, 0xdebadd51, 0x6e114dcf, 0xd8212cf8, 0x2a6a57d2, 0xb9667ed8, 0x93817fef, 0x639cd8b4, 0x52b67bce, 0x681ee61c, 0x50bb5414, 0x9f297765, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1d, 0x6f51, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x12b54404, 0x9ad5df58, 0x8a73af2a, 0xd89dd454, 0x57f5a14b, 0xcee0a06f, 0xb53e67ca, 0x92730368, 0xab12a843, 0x929ae15d, 0xea1e4f49, 0xd7fadfbc, 0x9defdd99, 0xff22c9c8, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0x12b54404, 0x9ad5df58, 0x8a73af2a, 0xd89dd454, 0x57f5a14b, 0xcee0a06f, 0xb53e67ca, 0x92730368, 0xab12a843, 0x929ae15d, 0xea1e4f49, 0xd7fadfbc, 0x9defdd99, 0xff22c9c8, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea15, 0x5f77, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x64dfdc90, 0xd570bc25, 0xae804ff7, 0x491ad040, 0xfe5f6d58, 0x850c1223, 0x39afac7b, 0xcc5a165a, 0x956a9473, 0x89d3941f, 0x6e520e57, 0x804dca75, 0xbd40cde8, 0xff68c0e7, 0x00000000, 0x700001f0 },
|
||||
FinalRegs = new uint[] { 0x64dfdc90, 0xd570bc25, 0xae804ff7, 0x491ad040, 0xfe5f6d58, 0x850c1223, 0x39afac7b, 0xcc5a165a, 0x956a9473, 0x89d3941f, 0x6e520e57, 0x804dca75, 0xbd40cde8, 0xff68c0e7, 0x00000000, 0xb00001d0 },
|
||||
},
|
||||
// ADD (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1c, 0x4f0b, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x8a81d956, 0x65b2b25b, 0x34dd981e, 0x924542f4, 0xeed4b95a, 0x096d832c, 0x8ddcb715, 0x2df1897b, 0x696d0d5c, 0xfa6853c1, 0xcbb52912, 0xe37a3fda, 0x54dd595d, 0x652e5a2b, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0x8a81d956, 0x65b2b25b, 0x34dd981e, 0x924542f4, 0xeed4b95a, 0x096d832c, 0x8ddcb715, 0x2df1897b, 0x696d0d5c, 0xfa6853c1, 0xcbb52912, 0xe37a3fda, 0x54dd595d, 0x652e5a2b, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea12, 0x6faa, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x4b774bbf, 0xe1f3168c, 0xfcdf56d4, 0x0a9feca9, 0x8d832cd1, 0x27af5bb2, 0xe7123c8f, 0x5ae971a8, 0x7c86287f, 0x5e69f0a7, 0x43e672d3, 0xb552a0f4, 0xb8b4fc17, 0xa9cc9a9d, 0x00000000, 0x400001f0 },
|
||||
FinalRegs = new uint[] { 0x4b774bbf, 0xe1f3168c, 0xfcdf56d4, 0x0a9feca9, 0x8d832cd1, 0x27af5bb2, 0xe7123c8f, 0x5ae971a8, 0x7c86287f, 0x5e69f0a7, 0x43e672d3, 0xb552a0f4, 0xb8b4fc17, 0xa9cc9a9d, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea18, 0x2f2c, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x907613be, 0x807d314f, 0x10328bb5, 0xf3433f78, 0x0fa6c190, 0x473e8ac5, 0x0019b12e, 0xa24d7590, 0x0fdac8d5, 0x24e4feea, 0xf5eadcbf, 0xdfd73f71, 0xee2c8957, 0xaef12e15, 0x00000000, 0x100001f0 },
|
||||
FinalRegs = new uint[] { 0x907613be, 0x807d314f, 0x10328bb5, 0xf3433f78, 0x0fa6c190, 0x473e8ac5, 0x0019b12e, 0xa24d7590, 0x0fdac8d5, 0x24e4feea, 0xf5eadcbf, 0xdfd73f71, 0xee2c8957, 0xaef12e15, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea16, 0x3f00, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x2f9a7f7c, 0xc6ad7d01, 0x12b220a4, 0x57b4e83c, 0x2132b566, 0xb4afd045, 0x2b5d39bf, 0xceeecd89, 0x724bff21, 0xb527620e, 0xa9fba943, 0xd2d70658, 0x4e69f57b, 0x55df6b8f, 0x00000000, 0xf00001f0 },
|
||||
FinalRegs = new uint[] { 0x2f9a7f7c, 0xc6ad7d01, 0x12b220a4, 0x57b4e83c, 0x2132b566, 0xb4afd045, 0x2b5d39bf, 0xceeecd89, 0x724bff21, 0xb527620e, 0xa9fba943, 0xd2d70658, 0x4e69f57b, 0x55df6b8f, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea16, 0x2fdb, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x854d0c91, 0x895ded29, 0x3e81cd89, 0x9ed269cf, 0x8a7354fa, 0x95cfe79f, 0x07248663, 0x3ec81b86, 0x6f1086e0, 0x51b4c91c, 0xb2d0946b, 0x1b81a616, 0x2b03fe57, 0xfbde03fd, 0x00000000, 0x700001f0 },
|
||||
FinalRegs = new uint[] { 0x854d0c91, 0x895ded29, 0x3e81cd89, 0x9ed269cf, 0x8a7354fa, 0x95cfe79f, 0x07248663, 0x3ec81b86, 0x6f1086e0, 0x51b4c91c, 0xb2d0946b, 0x1b81a616, 0x2b03fe57, 0xfbde03fd, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
// ADC (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x3fe4, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x4333cbde, 0xb61e8731, 0x2121c6ec, 0x796ecc75, 0xb570472d, 0x203aa9ea, 0xdad7bf7e, 0x93654919, 0x9e4c4e08, 0x1e352004, 0x104a06d7, 0x6b9a4b8a, 0xa0bcd372, 0x1713789a, 0x00000000, 0xb00001f0 },
|
||||
FinalRegs = new uint[] { 0x4333cbde, 0xb61e8731, 0x2121c6ec, 0x796ecc75, 0xb570472d, 0x203aa9ea, 0xdad7bf7e, 0x93654919, 0x9e4c4e08, 0x1e352004, 0x104a06d7, 0x6b9a4b8a, 0xa0bcd372, 0x1713789a, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x3f40, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xad935c4c, 0x4c7dcbfc, 0x802965ca, 0x1dccd2e8, 0xe960e9e1, 0x3bff0055, 0x204f6a43, 0xe31b010d, 0x33c8f3c5, 0x8e27b912, 0x1351e4a6, 0x90195167, 0xd9112661, 0xee319b51, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0xad935c4c, 0x4c7dcbfc, 0x802965ca, 0x1dccd2e8, 0xe960e9e1, 0x3bff0055, 0x204f6a43, 0xe31b010d, 0x33c8f3c5, 0x8e27b912, 0x1351e4a6, 0x90195167, 0xd9112661, 0xee319b51, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea15, 0x3fe8, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x26deb79f, 0x01ac9258, 0x5982ae34, 0x2c3c6df6, 0xabf6a749, 0x319d4b48, 0xce8cca6d, 0xe4b70851, 0x135c049c, 0xd8839d5e, 0xd3171a30, 0xfb09e096, 0x06d67a32, 0x0bab9825, 0x00000000, 0x100001f0 },
|
||||
FinalRegs = new uint[] { 0x26deb79f, 0x01ac9258, 0x5982ae34, 0x2c3c6df6, 0xabf6a749, 0x319d4b48, 0xce8cca6d, 0xe4b70851, 0x135c049c, 0xd8839d5e, 0xd3171a30, 0xfb09e096, 0x06d67a32, 0x0bab9825, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea12, 0x2fec, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xd2c9ebca, 0x3ace22dc, 0x41603f45, 0x8cc7e2c2, 0xa160b815, 0xac1e7eb8, 0x57c49232, 0x62547a9b, 0xa18407fd, 0x5c549424, 0xf3eec0e1, 0x4185299f, 0x329a9063, 0x649d9b44, 0x00000000, 0x900001f0 },
|
||||
FinalRegs = new uint[] { 0xd2c9ebca, 0x3ace22dc, 0x41603f45, 0x8cc7e2c2, 0xa160b815, 0xac1e7eb8, 0x57c49232, 0x62547a9b, 0xa18407fd, 0x5c549424, 0xf3eec0e1, 0x4185299f, 0x329a9063, 0x649d9b44, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea15, 0x2f73, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xf3f8be7b, 0x05e02ec4, 0x78bd9fce, 0x6fb329c8, 0x3094f103, 0x7c93c61d, 0xaade3d9f, 0x381bf77c, 0x738389cd, 0xc68dc0e2, 0x60a06e86, 0x9b717afd, 0x9e51e4eb, 0xf7966699, 0x00000000, 0x500001f0 },
|
||||
FinalRegs = new uint[] { 0xf3f8be7b, 0x05e02ec4, 0x78bd9fce, 0x6fb329c8, 0x3094f103, 0x7c93c61d, 0xaade3d9f, 0x381bf77c, 0x738389cd, 0xc68dc0e2, 0x60a06e86, 0x9b717afd, 0x9e51e4eb, 0xf7966699, 0x00000000, 0x300001d0 },
|
||||
},
|
||||
// SBC (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1d, 0x3faa, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xd839dfb1, 0x7cc5425c, 0xc55b5255, 0x37b845aa, 0x59f892d4, 0x7ef8e6ec, 0x3491a7fb, 0x87b88546, 0x72b4c444, 0x24cf48d7, 0x7f530fb7, 0x5b243902, 0x6c39c38f, 0x10e3165c, 0x00000000, 0x200001f0 },
|
||||
FinalRegs = new uint[] { 0xd839dfb1, 0x7cc5425c, 0xc55b5255, 0x37b845aa, 0x59f892d4, 0x7ef8e6ec, 0x3491a7fb, 0x87b88546, 0x72b4c444, 0x24cf48d7, 0x7f530fb7, 0x5b243902, 0x6c39c38f, 0x10e3165c, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea12, 0x1f7d, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xcd24aba8, 0x1d7d0523, 0xc150da45, 0x2f7eb96b, 0x9c1ed9af, 0x75056b89, 0x91c818d1, 0x8a07d574, 0x67ff1d4a, 0x6aca4429, 0xc4b5fb7c, 0x21e9ca50, 0xb95cbd15, 0xce3752e7, 0x00000000, 0xb00001f0 },
|
||||
FinalRegs = new uint[] { 0xcd24aba8, 0x1d7d0523, 0xc150da45, 0x2f7eb96b, 0x9c1ed9af, 0x75056b89, 0x91c818d1, 0x8a07d574, 0x67ff1d4a, 0x6aca4429, 0xc4b5fb7c, 0x21e9ca50, 0xb95cbd15, 0xce3752e7, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea10, 0x2fc2, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x401285a0, 0x7ab1e348, 0xf48a2615, 0x46d49913, 0xff5e9911, 0x4b4d7920, 0x8e1f921e, 0x05075455, 0x24e4acea, 0x8652e355, 0x11d0fe46, 0x0cfe7c08, 0xf326adee, 0x7fcde7ac, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0x401285a0, 0x7ab1e348, 0xf48a2615, 0x46d49913, 0xff5e9911, 0x4b4d7920, 0x8e1f921e, 0x05075455, 0x24e4acea, 0x8652e355, 0x11d0fe46, 0x0cfe7c08, 0xf326adee, 0x7fcde7ac, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea16, 0x3f22, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x5bb504b3, 0x3dd293c9, 0x3b2b2d7c, 0x30c2876a, 0x1c99a70e, 0x741294e7, 0xfd5f7315, 0x0149b9db, 0x3975aa1c, 0x9269e207, 0xdc42fd14, 0xea6a1c89, 0xa03e7d65, 0x171c30ad, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0x5bb504b3, 0x3dd293c9, 0x3b2b2d7c, 0x30c2876a, 0x1c99a70e, 0x741294e7, 0xfd5f7315, 0x0149b9db, 0x3975aa1c, 0x9269e207, 0xdc42fd14, 0xea6a1c89, 0xa03e7d65, 0x171c30ad, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x1f86, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xe0f5bbe3, 0xd96f3c62, 0x11944b25, 0x372e4b0e, 0x7c956b35, 0x03df46ac, 0x8f11684b, 0x3044502e, 0x6ebf2992, 0x4f3a0366, 0x9f36f014, 0x4c55f6aa, 0x6473e494, 0x8b6310d6, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0xe0f5bbe3, 0xd96f3c62, 0x11944b25, 0x372e4b0e, 0x7c956b35, 0x03df46ac, 0x8f11684b, 0x3044502e, 0x6ebf2992, 0x4f3a0366, 0x9f36f014, 0x4c55f6aa, 0x6473e494, 0x8b6310d6, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
// CMP (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x6f45, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xe72e848a, 0x97499b66, 0xcde944bc, 0xf6a7e4e1, 0xd8860029, 0xc55c7e43, 0x58dc13d7, 0x5e1cf6ac, 0x8094a819, 0xdba64363, 0xd8f5423f, 0x6ae843f0, 0x69766600, 0x2814e4e6, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0xe72e848a, 0x97499b66, 0xcde944bc, 0xf6a7e4e1, 0xd8860029, 0xc55c7e43, 0x58dc13d7, 0x5e1cf6ac, 0x8094a819, 0xdba64363, 0xd8f5423f, 0x6ae843f0, 0x69766600, 0x2814e4e6, 0x00000000, 0x800001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x7fd8, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x323e5ef9, 0x46e23bdf, 0x8d69d89a, 0x9ffddd37, 0x53f4a07b, 0xe923f9bb, 0x5ea62678, 0x1709127c, 0xc0c20492, 0x0ee47a0c, 0xe137cc2e, 0x7d72db37, 0xca9eb971, 0x4447b224, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0x323e5ef9, 0x46e23bdf, 0x8d69d89a, 0x9ffddd37, 0x53f4a07b, 0xe923f9bb, 0x5ea62678, 0x1709127c, 0xc0c20492, 0x0ee47a0c, 0xe137cc2e, 0x7d72db37, 0xca9eb971, 0x4447b224, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea10, 0x3f43, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x4cce7ac7, 0x03055e03, 0x479ec669, 0x8b1d9783, 0xa59509e1, 0xa46866ef, 0x654578c4, 0x700e322b, 0xa4191329, 0xb1b8479a, 0xe555a2ce, 0x1ef22472, 0xd41fb2ae, 0x2d794684, 0x00000000, 0x200001f0 },
|
||||
FinalRegs = new uint[] { 0x4cce7ac7, 0x03055e03, 0x479ec669, 0x8b1d9783, 0xa59509e1, 0xa46866ef, 0x654578c4, 0x700e322b, 0xa4191329, 0xb1b8479a, 0xe555a2ce, 0x1ef22472, 0xd41fb2ae, 0x2d794684, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea18, 0x7fd2, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xeecfbfb2, 0xbe6288fd, 0x34c0fc94, 0x3a01b105, 0xe7dc6252, 0xc05813fa, 0x6613d82d, 0x90dc7a0c, 0x34637299, 0x58f6d0e7, 0xb151d65e, 0xca975eca, 0xf83b6533, 0x10177f01, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0xeecfbfb2, 0xbe6288fd, 0x34c0fc94, 0x3a01b105, 0xe7dc6252, 0xc05813fa, 0x6613d82d, 0x90dc7a0c, 0x34637299, 0x58f6d0e7, 0xb151d65e, 0xca975eca, 0xf83b6533, 0x10177f01, 0x00000000, 0x400001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x2f6e, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x54dcd0c5, 0x629131da, 0xc4010f0b, 0xf28a7f0f, 0x866d92a3, 0xbb9a1b32, 0xc8c7f425, 0x8d13d61f, 0x1f9a5d13, 0x83e0b2b7, 0x7ef44e14, 0x24c291a3, 0x851cc882, 0x31a056cb, 0x00000000, 0xb00001f0 },
|
||||
FinalRegs = new uint[] { 0x54dcd0c5, 0x629131da, 0xc4010f0b, 0xf28a7f0f, 0x866d92a3, 0xbb9a1b32, 0xc8c7f425, 0x8d13d61f, 0x1f9a5d13, 0x83e0b2b7, 0x7ef44e14, 0x24c291a3, 0x851cc882, 0x31a056cb, 0x00000000, 0x500001d0 },
|
||||
},
|
||||
// SUB (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x6f56, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xf997cd9f, 0xf94c5bd7, 0x5411a289, 0x21311b8f, 0xee8a1fe4, 0x73808b62, 0x4daadf68, 0x14a1c57c, 0x92d98c4c, 0x31f999c9, 0x953b94b9, 0x108acc75, 0xcc38ea73, 0x5dc27e61, 0x00000000, 0x600001f0 },
|
||||
FinalRegs = new uint[] { 0xf997cd9f, 0xf94c5bd7, 0x5411a289, 0x21311b8f, 0xee8a1fe4, 0x73808b62, 0x4daadf68, 0x14a1c57c, 0x92d98c4c, 0x31f999c9, 0x953b94b9, 0x108acc75, 0xcc38ea73, 0x5dc27e61, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea19, 0x0f94, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x5f8de6f4, 0x09e82020, 0x480dc701, 0xd3303ca3, 0x8739e87a, 0x3da0b6d2, 0x10093787, 0xd30606fc, 0xd81d45da, 0xa66f5e86, 0xd8ddf48e, 0xa8321bd1, 0x62a75c1c, 0x3cffac30, 0x00000000, 0x800001f0 },
|
||||
FinalRegs = new uint[] { 0x5f8de6f4, 0x09e82020, 0x480dc701, 0xd3303ca3, 0x8739e87a, 0x3da0b6d2, 0x10093787, 0xd30606fc, 0xd81d45da, 0xa66f5e86, 0xd8ddf48e, 0xa8321bd1, 0x62a75c1c, 0x3cffac30, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea14, 0x7fc6, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x001f39cf, 0x76b925c8, 0x292b283a, 0x9d142282, 0x2cda04fa, 0x87f29de5, 0x9e9a98e4, 0x9d48ddbb, 0x9ea329fd, 0x653f2346, 0xfc116785, 0x6e565e16, 0x9a7f8c11, 0x46f1ecbb, 0x00000000, 0xd00001f0 },
|
||||
FinalRegs = new uint[] { 0x001f39cf, 0x76b925c8, 0x292b283a, 0x9d142282, 0x2cda04fa, 0x87f29de5, 0x9e9a98e4, 0x9d48ddbb, 0x9ea329fd, 0x653f2346, 0xfc116785, 0x6e565e16, 0x9a7f8c11, 0x46f1ecbb, 0x00000000, 0x500001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea19, 0x5fa5, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xfb6a4a50, 0xd074ee0e, 0x599131ef, 0x5db48236, 0xf287fcd1, 0xadea3b9f, 0xf2529f30, 0x6717a5af, 0xe1a3bc40, 0xd92e291b, 0x9b0337eb, 0xcab803ed, 0x255dd8a9, 0xea0e7824, 0x00000000, 0xb00001f0 },
|
||||
FinalRegs = new uint[] { 0xfb6a4a50, 0xd074ee0e, 0x599131ef, 0x5db48236, 0xf287fcd1, 0xadea3b9f, 0xf2529f30, 0x6717a5af, 0xe1a3bc40, 0xd92e291b, 0x9b0337eb, 0xcab803ed, 0x255dd8a9, 0xea0e7824, 0x00000000, 0xb00001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1c, 0x6f86, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x3a000492, 0xc16be6fa, 0x20053393, 0x597617c9, 0xc30c0ac0, 0x0ed34739, 0xf964a3d4, 0x4dcf9b40, 0x93109692, 0x7ed22040, 0x1f57a26e, 0x008d29d2, 0x99b2dae8, 0xe8a14948, 0x00000000, 0x200001f0 },
|
||||
FinalRegs = new uint[] { 0x3a000492, 0xc16be6fa, 0x20053393, 0x597617c9, 0xc30c0ac0, 0x0ed34739, 0xf964a3d4, 0x4dcf9b40, 0x93109692, 0x7ed22040, 0x1f57a26e, 0x008d29d2, 0x99b2dae8, 0xe8a14948, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
// RSB (reg)
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x6f72, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x9a603e20, 0x10004fe3, 0x8c33bcef, 0x8a23db09, 0x47244c0c, 0x53417661, 0x6486ac8b, 0x5276c43b, 0x577f49a7, 0x34542492, 0xb4ac7c99, 0x5de5cb55, 0x8f6e1d72, 0x077d4a02, 0x00000000, 0xe00001f0 },
|
||||
FinalRegs = new uint[] { 0x9a603e20, 0x10004fe3, 0x8c33bcef, 0x8a23db09, 0x47244c0c, 0x53417661, 0x6486ac8b, 0x5276c43b, 0x577f49a7, 0x34542492, 0xb4ac7c99, 0x5de5cb55, 0x8f6e1d72, 0x077d4a02, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x0ff3, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x6fdd73d7, 0xc6c4c438, 0x772312e2, 0xa57de93f, 0xa1edd64b, 0x8ee41d33, 0x85849a41, 0xac34953a, 0xb3d7c6b5, 0x439ceff1, 0xa3096172, 0x5d8f0654, 0x2e2993a3, 0xca221149, 0x00000000, 0x400001f0 },
|
||||
FinalRegs = new uint[] { 0x6fdd73d7, 0xc6c4c438, 0x772312e2, 0xa57de93f, 0xa1edd64b, 0x8ee41d33, 0x85849a41, 0xac34953a, 0xb3d7c6b5, 0x439ceff1, 0xa3096172, 0x5d8f0654, 0x2e2993a3, 0xca221149, 0x00000000, 0x200001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1b, 0x1f34, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0xbefd78a5, 0x6d071150, 0xe9ce2c88, 0x2251ed54, 0x30610b17, 0x6428697e, 0xf6e940a4, 0x2395634f, 0xdabff1a3, 0x89988d57, 0x85dd20b0, 0x2ca1311d, 0xcd0748d9, 0xedf55a6f, 0x00000000, 0x800001f0 },
|
||||
FinalRegs = new uint[] { 0xbefd78a5, 0x6d071150, 0xe9ce2c88, 0x2251ed54, 0x30610b17, 0x6428697e, 0xf6e940a4, 0x2395634f, 0xdabff1a3, 0x89988d57, 0x85dd20b0, 0x2ca1311d, 0xcd0748d9, 0xedf55a6f, 0x00000000, 0x000001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea16, 0x5f83, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x57018e40, 0xc4027d19, 0x33a32bd7, 0x6a75787a, 0x18f8569a, 0xbbf3a50d, 0x7f35656f, 0x66fbdad7, 0x3aa48c57, 0x39709ea2, 0x5972e4ba, 0xb2c2c772, 0x52f35620, 0x7ef9f1d6, 0x00000000, 0xd00001f0 },
|
||||
FinalRegs = new uint[] { 0x57018e40, 0xc4027d19, 0x33a32bd7, 0x6a75787a, 0x18f8569a, 0xbbf3a50d, 0x7f35656f, 0x66fbdad7, 0x3aa48c57, 0x39709ea2, 0x5972e4ba, 0xb2c2c772, 0x52f35620, 0x7ef9f1d6, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
new PrecomputedThumbTestCase()
|
||||
{
|
||||
Instructions = new ushort[] { 0xea1a, 0x0fd8, 0x4770, 0xe7fe },
|
||||
StartRegs = new uint[] { 0x79108ff6, 0x0cb1e662, 0x9eb9ffed, 0x1ee4d3de, 0x7a8fa20a, 0x1db7e216, 0x6fc42752, 0x9cb6cdad, 0xa497a582, 0x654c446f, 0xcbb31efc, 0x601e6995, 0xe328af35, 0x824026e7, 0x00000000, 0xd00001f0 },
|
||||
FinalRegs = new uint[] { 0x79108ff6, 0x0cb1e662, 0x9eb9ffed, 0x1ee4d3de, 0x7a8fa20a, 0x1db7e216, 0x6fc42752, 0x9cb6cdad, 0xa497a582, 0x654c446f, 0xcbb31efc, 0x601e6995, 0xe328af35, 0x824026e7, 0x00000000, 0x100001d0 },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
9
Ryujinx.Tests/Cpu/PrecomputedThumbTestCase.cs
Normal file
9
Ryujinx.Tests/Cpu/PrecomputedThumbTestCase.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Tests.Cpu
|
||||
{
|
||||
public class PrecomputedThumbTestCase
|
||||
{
|
||||
public ushort[] Instructions;
|
||||
public uint[] StartRegs;
|
||||
public uint[] FinalRegs;
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
|
||||
/// <summary>
|
||||
/// The current version of the file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 35;
|
||||
public const int CurrentVersion = 36;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the configuration file format
|
||||
@ -81,6 +81,11 @@ namespace Ryujinx.Configuration
|
||||
/// </summary>
|
||||
public bool LoggingEnableError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables printing trace log messages
|
||||
/// </summary>
|
||||
public bool LoggingEnableTrace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables printing guest log messages
|
||||
/// </summary>
|
||||
|
@ -129,6 +129,11 @@ namespace Ryujinx.Configuration
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableError { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables printing trace log messages
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableTrace { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables printing guest log messages
|
||||
/// </summary>
|
||||
@ -161,6 +166,7 @@ namespace Ryujinx.Configuration
|
||||
EnableInfo = new ReactiveObject<bool>();
|
||||
EnableWarn = new ReactiveObject<bool>();
|
||||
EnableError = new ReactiveObject<bool>();
|
||||
EnableTrace = new ReactiveObject<bool>();
|
||||
EnableGuest = new ReactiveObject<bool>();
|
||||
EnableFsAccessLog = new ReactiveObject<bool>();
|
||||
FilteredClasses = new ReactiveObject<LogClass[]>();
|
||||
@ -455,6 +461,7 @@ namespace Ryujinx.Configuration
|
||||
LoggingEnableInfo = Logger.EnableInfo,
|
||||
LoggingEnableWarn = Logger.EnableWarn,
|
||||
LoggingEnableError = Logger.EnableError,
|
||||
LoggingEnableTrace = Logger.EnableTrace,
|
||||
LoggingEnableGuest = Logger.EnableGuest,
|
||||
LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
|
||||
LoggingFilteredClasses = Logger.FilteredClasses,
|
||||
@ -526,6 +533,7 @@ namespace Ryujinx.Configuration
|
||||
Logger.EnableInfo.Value = true;
|
||||
Logger.EnableWarn.Value = true;
|
||||
Logger.EnableError.Value = true;
|
||||
Logger.EnableTrace.Value = false;
|
||||
Logger.EnableGuest.Value = true;
|
||||
Logger.EnableFsAccessLog.Value = false;
|
||||
Logger.FilteredClasses.Value = Array.Empty<LogClass>();
|
||||
@ -990,6 +998,15 @@ namespace Ryujinx.Configuration
|
||||
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
if (configurationFileFormat.Version < 36)
|
||||
{
|
||||
Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 36.");
|
||||
|
||||
configurationFileFormat.LoggingEnableTrace = false;
|
||||
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
|
||||
Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading;
|
||||
@ -1003,6 +1020,7 @@ namespace Ryujinx.Configuration
|
||||
Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo;
|
||||
Logger.EnableWarn.Value = configurationFileFormat.LoggingEnableWarn;
|
||||
Logger.EnableError.Value = configurationFileFormat.LoggingEnableError;
|
||||
Logger.EnableTrace.Value = configurationFileFormat.LoggingEnableTrace;
|
||||
Logger.EnableGuest.Value = configurationFileFormat.LoggingEnableGuest;
|
||||
Logger.EnableFsAccessLog.Value = configurationFileFormat.LoggingEnableFsAccessLog;
|
||||
Logger.FilteredClasses.Value = configurationFileFormat.LoggingFilteredClasses;
|
||||
|
@ -13,6 +13,7 @@ namespace Ryujinx.Configuration
|
||||
ConfigurationState.Instance.Logger.EnableInfo.Event += ReloadEnableInfo;
|
||||
ConfigurationState.Instance.Logger.EnableWarn.Event += ReloadEnableWarning;
|
||||
ConfigurationState.Instance.Logger.EnableError.Event += ReloadEnableError;
|
||||
ConfigurationState.Instance.Logger.EnableTrace.Event += ReloadEnableTrace;
|
||||
ConfigurationState.Instance.Logger.EnableGuest.Event += ReloadEnableGuest;
|
||||
ConfigurationState.Instance.Logger.EnableFsAccessLog.Event += ReloadEnableFsAccessLog;
|
||||
ConfigurationState.Instance.Logger.FilteredClasses.Event += ReloadFilteredClasses;
|
||||
@ -44,6 +45,11 @@ namespace Ryujinx.Configuration
|
||||
Logger.SetEnable(LogLevel.Error, e.NewValue);
|
||||
}
|
||||
|
||||
private static void ReloadEnableTrace(object sender, ReactiveEventArgs<bool> e)
|
||||
{
|
||||
Logger.SetEnable(LogLevel.Trace, e.NewValue);
|
||||
}
|
||||
|
||||
private static void ReloadEnableGuest(object sender, ReactiveEventArgs<bool> e)
|
||||
{
|
||||
Logger.SetEnable(LogLevel.Guest, e.NewValue);
|
||||
@ -74,7 +80,7 @@ namespace Ryujinx.Configuration
|
||||
if (e.NewValue)
|
||||
{
|
||||
Logger.AddTarget(new AsyncLogTargetWrapper(
|
||||
new FileLogTarget(AppDomain.CurrentDomain.BaseDirectory, "file"),
|
||||
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
|
||||
1000,
|
||||
AsyncLogTargetOverflowAction.Block
|
||||
));
|
||||
|
@ -311,7 +311,7 @@ namespace Ryujinx.Modules
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, e.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, $"Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
|
||||
|
||||
@ -327,8 +327,8 @@ namespace Ryujinx.Modules
|
||||
catch (WebException ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, $"Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
for (int j = 0; j < webClients.Count; j++)
|
||||
{
|
||||
webClients[j].CancelAsync();
|
||||
@ -567,7 +567,14 @@ namespace Ryujinx.Modules
|
||||
#else
|
||||
if (showWarnings)
|
||||
{
|
||||
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
|
||||
if (ReleaseInformations.IsFlatHubBuild())
|
||||
{
|
||||
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub.");
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -27,6 +27,8 @@ namespace Ryujinx
|
||||
|
||||
public static string ConfigurationPath { get; set; }
|
||||
|
||||
public static string CommandLineProfile { get; set; }
|
||||
|
||||
[DllImport("libX11")]
|
||||
private extern static int XInitThreads();
|
||||
|
||||
@ -52,6 +54,17 @@ namespace Ryujinx
|
||||
|
||||
baseDirPathArg = args[++i];
|
||||
}
|
||||
else if (arg == "-p" || arg == "--profile")
|
||||
{
|
||||
if (i + 1 >= args.Length)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
CommandLineProfile = args[++i];
|
||||
}
|
||||
else if (arg == "-f" || arg == "--fullscreen")
|
||||
{
|
||||
startFullscreenArg = true;
|
||||
|
@ -242,15 +242,18 @@ namespace Ryujinx.Ui.App
|
||||
}
|
||||
else
|
||||
{
|
||||
// Store the ControlFS in variable called controlFs
|
||||
GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId);
|
||||
|
||||
// Check if there is an update available.
|
||||
if (IsUpdateApplied(titleId, out IFileSystem updatedControlFs))
|
||||
{
|
||||
// Replace the original ControlFs by the updated one.
|
||||
controlFs = updatedControlFs;
|
||||
}
|
||||
|
||||
ReadControlData(controlFs, controlHolder.ByteSpan);
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = IsUpdateApplied(titleId, out string updateVersion) ? updateVersion : controlHolder.Value.DisplayVersion.ToString();
|
||||
|
||||
GetNameIdDeveloper(ref controlHolder.Value, out titleName, out _, out developer);
|
||||
GetGameInformation(ref controlHolder.Value, out titleName, out _, out developer, out version);
|
||||
|
||||
// Read the icon from the ControlFS and store it as a byte array
|
||||
try
|
||||
@ -351,10 +354,7 @@ namespace Ryujinx.Ui.App
|
||||
// Read the NACP data
|
||||
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlHolder.Value.DisplayVersion.ToString();
|
||||
|
||||
GetNameIdDeveloper(ref controlHolder.Value, out titleName, out titleId, out developer);
|
||||
GetGameInformation(ref controlHolder.Value, out titleName, out titleId, out developer, out version);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -554,14 +554,14 @@ namespace Ryujinx.Ui.App
|
||||
return readableString;
|
||||
}
|
||||
|
||||
private void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher)
|
||||
private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
|
||||
{
|
||||
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
||||
|
||||
if (controlData.Titles.Length > (int)desiredTitleLanguage)
|
||||
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
|
||||
{
|
||||
titleName = controlData.Titles[(int)desiredTitleLanguage].Name.ToString();
|
||||
publisher = controlData.Titles[(int)desiredTitleLanguage].Publisher.ToString();
|
||||
titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
|
||||
publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -571,11 +571,11 @@ namespace Ryujinx.Ui.App
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
foreach (ApplicationControlTitle controlTitle in controlData.Titles)
|
||||
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
|
||||
{
|
||||
if (!((U8Span)controlTitle.Name).IsEmpty())
|
||||
if (!controlTitle.NameString.IsEmpty())
|
||||
{
|
||||
titleName = controlTitle.Name.ToString();
|
||||
titleName = controlTitle.NameString.ToString();
|
||||
|
||||
break;
|
||||
}
|
||||
@ -584,11 +584,11 @@ namespace Ryujinx.Ui.App
|
||||
|
||||
if (string.IsNullOrWhiteSpace(publisher))
|
||||
{
|
||||
foreach (ApplicationControlTitle controlTitle in controlData.Titles)
|
||||
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
|
||||
{
|
||||
if (!((U8Span)controlTitle.Publisher).IsEmpty())
|
||||
if (!controlTitle.PublisherString.IsEmpty())
|
||||
{
|
||||
publisher = controlTitle.Publisher.ToString();
|
||||
publisher = controlTitle.PublisherString.ToString();
|
||||
|
||||
break;
|
||||
}
|
||||
@ -599,7 +599,7 @@ namespace Ryujinx.Ui.App
|
||||
{
|
||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||
}
|
||||
else if (controlData.SaveDataOwnerId.Value != 0)
|
||||
else if (controlData.SaveDataOwnerId != 0)
|
||||
{
|
||||
titleId = controlData.SaveDataOwnerId.ToString();
|
||||
}
|
||||
@ -611,10 +611,14 @@ namespace Ryujinx.Ui.App
|
||||
{
|
||||
titleId = "0000000000000000";
|
||||
}
|
||||
|
||||
version = controlData.DisplayVersionString.ToString();
|
||||
}
|
||||
|
||||
private bool IsUpdateApplied(string titleId, out string version)
|
||||
private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)
|
||||
{
|
||||
updatedControlFs = null;
|
||||
|
||||
string updatePath = "(unknown)";
|
||||
|
||||
try
|
||||
@ -623,14 +627,7 @@ namespace Ryujinx.Ui.App
|
||||
|
||||
if (patchNca != null && controlNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new ApplicationControlProperty();
|
||||
using var nacpFile = new UniqueRef<IFile>();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
version = controlData.DisplayVersion.ToString();
|
||||
updatedControlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -645,8 +642,6 @@ namespace Ryujinx.Ui.App
|
||||
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
|
||||
}
|
||||
|
||||
version = "";
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ namespace Ryujinx.Ui
|
||||
VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient);
|
||||
|
||||
_contentManager = new ContentManager(_virtualFileSystem);
|
||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
|
||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, Program.CommandLineProfile);
|
||||
_userChannelPersistence = new UserChannelPersistence();
|
||||
|
||||
// Instantiate GUI objects.
|
||||
@ -638,18 +638,18 @@ namespace Ryujinx.Ui
|
||||
[Conditional("RELEASE")]
|
||||
public void PerformanceCheck()
|
||||
{
|
||||
if (ConfigurationState.Instance.Logger.EnableDebug.Value)
|
||||
if (ConfigurationState.Instance.Logger.EnableTrace.Value)
|
||||
{
|
||||
MessageDialog debugWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
|
||||
{
|
||||
Title = "Ryujinx - Warning",
|
||||
Text = "You have debug logging enabled, which is designed to be used by developers only.",
|
||||
SecondaryText = "For optimal performance, it's recommended to disable debug logging. Would you like to disable debug logging now?"
|
||||
Text = "You have trace logging enabled, which is designed to be used by developers only.",
|
||||
SecondaryText = "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?"
|
||||
};
|
||||
|
||||
if (debugWarningDialog.Run() == (int)ResponseType.Yes)
|
||||
{
|
||||
ConfigurationState.Instance.Logger.EnableDebug.Value = false;
|
||||
ConfigurationState.Instance.Logger.EnableTrace.Value = false;
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
@ -1291,7 +1291,7 @@ namespace Ryujinx.Ui
|
||||
|
||||
private void OpenLogsFolder_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||||
string logPath = System.IO.Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "Logs");
|
||||
|
||||
new DirectoryInfo(logPath).Create();
|
||||
|
||||
|
@ -6,7 +6,6 @@ using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
@ -26,8 +25,6 @@ using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||
|
||||
namespace Ryujinx.Ui.Widgets
|
||||
{
|
||||
public partial class GameTableContextMenu : Menu
|
||||
@ -81,7 +78,7 @@ namespace Ryujinx.Ui.Widgets
|
||||
PopupAtPointer(null);
|
||||
}
|
||||
|
||||
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId)
|
||||
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
|
||||
{
|
||||
saveDataId = default;
|
||||
|
||||
@ -121,7 +118,7 @@ namespace Ryujinx.Ui.Widgets
|
||||
|
||||
Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||
|
||||
result = EnsureApplicationSaveData(_horizonClient.Fs, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
|
||||
result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user);
|
||||
|
||||
if (result.IsFailure())
|
||||
{
|
||||
@ -146,11 +143,9 @@ namespace Ryujinx.Ui.Widgets
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OpenSaveDir(SaveDataFilter saveDataFilter)
|
||||
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
|
||||
{
|
||||
saveDataFilter.SetProgramId(new ProgramId(_titleId));
|
||||
|
||||
if (!TryFindSaveData(_titleName, _titleId, _controlData, saveDataFilter, out ulong saveDataId))
|
||||
if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -439,26 +434,24 @@ namespace Ryujinx.Ui.Widgets
|
||||
//
|
||||
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
SaveDataFilter saveDataFilter = new SaveDataFilter();
|
||||
saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low));
|
||||
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||
var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default);
|
||||
|
||||
OpenSaveDir(saveDataFilter);
|
||||
OpenSaveDir(in saveDataFilter);
|
||||
}
|
||||
|
||||
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
SaveDataFilter saveDataFilter = new SaveDataFilter();
|
||||
saveDataFilter.SetSaveDataType(SaveDataType.Device);
|
||||
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
||||
|
||||
OpenSaveDir(saveDataFilter);
|
||||
OpenSaveDir(in saveDataFilter);
|
||||
}
|
||||
|
||||
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
SaveDataFilter saveDataFilter = new SaveDataFilter();
|
||||
saveDataFilter.SetSaveDataType(SaveDataType.Bcat);
|
||||
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
||||
|
||||
OpenSaveDir(saveDataFilter);
|
||||
OpenSaveDir(in saveDataFilter);
|
||||
}
|
||||
|
||||
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
|
||||
|
@ -35,6 +35,7 @@ namespace Ryujinx.Ui.Windows
|
||||
private float _previousVolumeLevel;
|
||||
|
||||
#pragma warning disable CS0649, IDE0044
|
||||
[GUI] CheckButton _traceLogToggle;
|
||||
[GUI] CheckButton _errorLogToggle;
|
||||
[GUI] CheckButton _warningLogToggle;
|
||||
[GUI] CheckButton _infoLogToggle;
|
||||
@ -141,6 +142,11 @@ namespace Ryujinx.Ui.Windows
|
||||
};
|
||||
|
||||
// Setup Currents.
|
||||
if (ConfigurationState.Instance.Logger.EnableTrace)
|
||||
{
|
||||
_traceLogToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableFileLog)
|
||||
{
|
||||
_fileLogToggle.Click();
|
||||
@ -487,6 +493,7 @@ namespace Ryujinx.Ui.Windows
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableTrace.Value = _traceLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableStub.Value = _stubLogToggle.Active;
|
||||
|
@ -2572,6 +2572,24 @@
|
||||
<property name="position">21</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_traceLogToggle">
|
||||
<property name="label" translatable="yes">Enable Trace Logs</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Enables printing trace log messages</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">22</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -105,7 +105,7 @@ namespace Ryujinx.Ui.Windows
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersion.ToString()} - {path}");
|
||||
RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersionString.ToString()} - {path}");
|
||||
radioButton.JoinGroup(_noUpdateRadioButton);
|
||||
|
||||
_availableUpdatesBox.Add(radioButton);
|
||||
|
1
distribution/linux/ryujinx-logo.svg
Normal file
1
distribution/linux/ryujinx-logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 255.76 255.76"><defs><style>.cls-1{fill:#02c5e5;}.cls-2{fill:#ff5f55;}.cls-3{fill:none;}</style></defs><g id="Ebene_2" data-name="Ebene 2"><g id="Ebene_1-2" data-name="Ebene 1"><g id="Ebene_2-2" data-name="Ebene 2"><g id="Ebene_1-2-2" data-name="Ebene 1-2"><path class="cls-1" d="M80.63,0V220.39H44.37c-14,0-35.74-20.74-35.74-39.13V40.13C8.63,19.19,31.36,0,49.06,0Z"/><path class="cls-2" d="M175.13,35.37V255.76h36.26c14,0,35.74-20.74,35.74-39.13V75.5c0-20.94-22.73-40.13-40.43-40.13Z"/><polygon class="cls-1" points="124.34 137.96 122.58 145.57 90.64 145.57 92.89 137.96 124.34 137.96"/><polygon class="cls-2" points="160.29 137.96 157.84 145.57 122.58 145.57 124.34 137.96 160.29 137.96"/><polygon class="cls-1" points="130.39 111.86 128.62 119.47 95.14 119.47 97.39 111.86 130.39 111.86"/><polygon class="cls-2" points="164.79 111.86 162.34 119.47 128.62 119.47 130.39 111.86 164.79 111.86"/><polygon class="cls-1" points="104.24 167.99 122.83 87.77 129.78 87.77 111.19 167.99 104.24 167.99"/><polygon class="cls-2" points="128.18 167.99 146.77 87.77 153.89 87.77 135.3 167.99 128.18 167.99"/></g><rect class="cls-3" width="255.76" height="255.76"/></g></g></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
23
distribution/linux/ryujinx-mime.xml
Normal file
23
distribution/linux/ryujinx-mime.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type type="application/x-nx-nca">
|
||||
<comment>Nintendo Content Archive</comment>
|
||||
<glob pattern="*.nca"/>
|
||||
</mime-type>
|
||||
<mime-type type="application/x-nx-nro">
|
||||
<comment>Nintendo Relocatable Object</comment>
|
||||
<glob pattern="*.nro"/>
|
||||
</mime-type>
|
||||
<mime-type type="application/x-nx-nso">
|
||||
<comment>Nintendo Shared Object</comment>
|
||||
<glob pattern="*.nso"/>
|
||||
</mime-type>
|
||||
<mime-type type="application/x-nx-nsp">
|
||||
<comment>Nintendo Submission Package</comment>
|
||||
<glob pattern="*.nsp"/>
|
||||
</mime-type>
|
||||
<mime-type type="application/x-nx-xci">
|
||||
<comment>Nintendo Switch Cartridge</comment>
|
||||
<glob pattern="*.xci"/>
|
||||
</mime-type>
|
||||
</mime-info>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user