Compare commits

...

8 Commits

Author SHA1 Message Date
747081d2c7 Decoders: Fix instruction lengths for 16-bit B instructions (#3177) 2022-03-05 16:20:24 +01:00
497199bb50 Decoder: Exit on trapping instructions, and resume execution at trapping instruction (#3153)
* Decoder: Exit on trapping instructions, and resume execution at trapping instruction

* Resume at trapping address

* remove mustExit
2022-03-04 23:16:58 +01:00
bd9ac0fdaa T32: Implement B, B.cond, BL, BLX (#3155)
* Decoders: Make IsThumb a function of OpCode32

* OpCode32: Fix GetPc

* T32: Implement B, B.cond, BL, BLX

* rm usings
2022-03-04 23:05:08 +01:00
ac21abbb9d Preparation for initial Flatpack and FlatHub integration (#3173)
* Preparation for initial Flatpack and FlatHub integration

This integrate some initial changes required for Flatpack and distribution from FlatHub.

Also added some resources that will be used for packaging on Linux.

* Address gdkchan comment
2022-03-04 18:03:16 +01:00
a3dd04deef Implement -p or --profile command line argument (#2947)
* Implement -p or --profile command line argument

* Put command line logic all in Program and reference it elsewhere

* Address feedback
2022-03-02 09:51:37 +01:00
3705c20668 Update LibHac to v0.16.0 (#3159) 2022-02-27 00:52:25 +01:00
7b35ebc64a T32: Implement ALU (shifted register) instructions (#3135)
* T32: Implement ADC, ADD, AND, BIC, CMN, CMP, EOR, MOV, MVN, ORN, ORR, RSB, SBC, SUB, TEQ, TST (shifted register)

* OpCodeTable: Sort T32 list

* Tests: Rename RandomTestCase to PrecomputedThumbTestCase

* T32: Tests for AluRsImm instructions

* fix nit

* fix nit 2
2022-02-22 19:11:28 -03:00
0a24aa6af2 Allow textures to have their data partially mapped (#2629)
* Allow textures to have their data partially mapped

* Explicitly check for invalid memory ranges on the MultiRangeList

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

View File

@ -121,7 +121,7 @@ namespace ARMeilleure.Decoders
currBlock.Branch = GetBlock((ulong)op.Immediate);
}
if (!IsUnconditionalBranch(lastOp) || isCall)
if (isCall || !(IsUnconditionalBranch(lastOp) || IsTrap(lastOp)))
{
currBlock.Next = GetBlock(currBlock.EndAddress);
}
@ -263,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;
}
@ -324,9 +329,13 @@ namespace ARMeilleure.Decoders
}
private static bool IsException(OpCode opCode)
{
return IsTrap(opCode) || opCode.Instruction.Name == InstName.Svc;
}
private static bool IsTrap(OpCode opCode)
{
return opCode.Instruction.Name == InstName.Brk ||
opCode.Instruction.Name == InstName.Svc ||
opCode.Instruction.Name == InstName.Trap ||
opCode.Instruction.Name == InstName.Und;
}

View File

@ -13,11 +13,25 @@ namespace ARMeilleure.Decoders
Cond = (Condition)((uint)opCode >> 28);
}
public bool IsThumb()
{
return this is OpCodeT16 || this is OpCodeT32;
}
public uint GetPc()
{
// Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline,
// the PC actually points 2 instructions ahead.
return (uint)Address + (uint)OpCodeSizeInBytes * 2;
if (IsThumb())
{
// PC is ahead by 4 in thumb mode whether or not the current instruction
// is 16 or 32 bit.
return (uint)Address + 4u;
}
else
{
return (uint)Address + 8u;
}
}
}
}

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16BImm11 : OpCode32, IOpCode32BImm
class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm
{
public long Immediate { get; }

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.Decoders
{
class OpCodeT16BImm8 : OpCode32, IOpCode32BImm
class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm
{
public long Immediate { get; }

View File

@ -0,0 +1,14 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32 : OpCode32
{
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32(inst, address, opCode);
public OpCodeT32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Cond = Condition.Al;
OpCodeSizeInBytes = 4;
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32Alu : OpCodeT32, IOpCode32Alu
{
public int Rd { get; }
public int Rn { get; }
public bool? SetFlags { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Alu(inst, address, opCode);
public OpCodeT32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rd = (opCode >> 8) & 0xf;
Rn = (opCode >> 16) & 0xf;
SetFlags = ((opCode >> 20) & 1) != 0;
}
}
}

View File

@ -0,0 +1,20 @@
namespace ARMeilleure.Decoders
{
class OpCodeT32AluRsImm : OpCodeT32Alu, IOpCode32AluRsImm
{
public int Rm { get; }
public int Immediate { get; }
public ShiftType ShiftType { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluRsImm(inst, address, opCode);
public OpCodeT32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
Rm = (opCode >> 0) & 0xf;
Immediate = ((opCode >> 6) & 3) | ((opCode >> 10) & 0x1c);
ShiftType = (ShiftType)((opCode >> 4) & 3);
}
}
}

View File

@ -0,0 +1,29 @@
using ARMeilleure.Instructions;
namespace ARMeilleure.Decoders
{
class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm
{
public long Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode);
public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
uint pc = GetPc();
int imm11 = (opCode >> 0) & 0x7ff;
int j2 = (opCode >> 11) & 1;
int j1 = (opCode >> 13) & 1;
int imm6 = (opCode >> 16) & 0x3f;
int s = (opCode >> 26) & 1;
int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19);
imm32 = (imm32 << 13) >> 12;
Immediate = pc + imm32;
Cond = (Condition)((opCode >> 22) & 0xf);
}
}
}

View File

@ -0,0 +1,35 @@
using ARMeilleure.Instructions;
namespace ARMeilleure.Decoders
{
class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm
{
public long Immediate { get; }
public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode);
public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode)
{
uint pc = GetPc();
if (inst.Name == InstName.Blx)
{
pc &= ~3u;
}
int imm11 = (opCode >> 0) & 0x7ff;
int j2 = (opCode >> 11) & 1;
int j1 = (opCode >> 13) & 1;
int imm10 = (opCode >> 16) & 0x3ff;
int s = (opCode >> 26) & 1;
int i1 = j1 ^ s ^ 1;
int i2 = j2 ^ s ^ 1;
int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23);
imm32 = (imm32 << 9) >> 8;
Immediate = pc + imm32;
}
}
}

View File

@ -1,6 +1,7 @@
using ARMeilleure.Instructions;
using 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,30 @@ 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("11110x<<<xxxxxxx10x0xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm20.Create);
SetT32("11110xxxxxxxxxxx10x1xxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT32BImm24.Create);
SetT32("11101010001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluRsImm.Create);
SetT32("11110xxxxxxxxxxx11x1xxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCodeT32BImm24.Create);
SetT32("11110xxxxxxxxxxx11x0xxxxxxxxxxx0", InstName.Blx, InstEmit32.Blx, OpCodeT32BImm24.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 +1116,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)

View File

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

View File

@ -128,7 +128,7 @@ namespace ARMeilleure.Instructions
{
Debug.Assert(value.Type == OperandType.I32);
if (IsThumb(context.CurrOp))
if (((OpCode32)context.CurrOp).IsThumb())
{
bool isReturn = IsA32Return(context);
if (!isReturn)

View File

@ -9,18 +9,25 @@ namespace ARMeilleure.Instructions
{
public static void Brk(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.Break));
OpCodeException op = (OpCodeException)context.CurrOp;
string name = nameof(NativeInterface.Break);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
context.LoadFromContext();
context.Return(Const(op.Address));
}
public static void Svc(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
}
private static void EmitExceptionCall(ArmEmitterContext context, string name)
{
OpCodeException op = (OpCodeException)context.CurrOp;
string name = nameof(NativeInterface.SupervisorCall);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id));
@ -41,6 +48,8 @@ namespace ARMeilleure.Instructions
context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.RawOpCode));
context.LoadFromContext();
context.Return(Const(op.Address));
}
}
}

View File

@ -9,19 +9,11 @@ namespace ARMeilleure.Instructions
static partial class InstEmit32
{
public static void Svc(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.SupervisorCall));
}
public static void Trap(ArmEmitterContext context)
{
EmitExceptionCall(context, nameof(NativeInterface.Break));
}
private static void EmitExceptionCall(ArmEmitterContext context, string name)
{
IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
string name = nameof(NativeInterface.SupervisorCall);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
@ -30,5 +22,20 @@ namespace ARMeilleure.Instructions
Translator.EmitSynchronization(context);
}
public static void Trap(ArmEmitterContext context)
{
IOpCode32Exception op = (IOpCode32Exception)context.CurrOp;
string name = nameof(NativeInterface.Break);
context.StoreToContext();
context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id));
context.LoadFromContext();
context.Return(Const(context.CurrOp.Address));
}
}
}

View File

@ -34,7 +34,7 @@ namespace ARMeilleure.Instructions
uint pc = op.GetPc();
bool isThumb = IsThumb(context.CurrOp);
bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
uint currentPc = isThumb
? pc | 1
@ -61,7 +61,7 @@ namespace ARMeilleure.Instructions
Operand addr = context.Copy(GetIntA32(context, op.Rm));
Operand bitOne = context.BitwiseAnd(addr, Const(1));
bool isThumb = IsThumb(context.CurrOp);
bool isThumb = ((OpCode32)context.CurrOp).IsThumb();
uint currentPc = isThumb
? (pc - 2) | 1

View File

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

View File

@ -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;
}
}
}

View File

@ -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>
@ -495,10 +505,20 @@ namespace Ryujinx.Graphics.Gpu.Image
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.
@ -668,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));
@ -784,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++)
@ -1079,6 +1107,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_textures.Remove(texture);
}
lock (_partiallyMappedTextures)
{
_partiallyMappedTextures.Remove(texture);
}
}
/// <summary>

View File

@ -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.

View File

@ -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;
@ -340,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))
@ -348,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));
@ -367,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;
@ -405,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;
}
}
}

View File

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

View File

@ -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())
{

View File

@ -466,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();
}

View File

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

View File

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

View File

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

View File

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

View File

@ -169,7 +169,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
// 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);

View File

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

View File

@ -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
};

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -1,4 +1,4 @@
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Fs;
using LibHac.Loader;
using LibHac.Tools.FsSystem;
@ -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)
{

View File

@ -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" />

View File

@ -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");
}
}
@ -396,7 +396,7 @@ namespace Ryujinx.Headless.SDL2
if ((bool)option.EnableFileLog)
{
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(AppDomain.CurrentDomain.BaseDirectory, "file"),
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
1000,
AsyncLogTargetOverflowAction.Block
));

View File

@ -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;
}

View File

@ -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>

View File

@ -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))
{

View File

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

View 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 },
},
};
}
}

View File

@ -0,0 +1,167 @@
using ARMeilleure.State;
using NUnit.Framework;
namespace Ryujinx.Tests.Cpu
{
[Category("T32Flow")]
public sealed class CpuTestT32Flow : CpuTest32
{
[Test]
public void TestT32B1()
{
// BNE label
ThumbOpcode(0xf040);
ThumbOpcode(0x8240);
for (int i = 0; i < 576; i++)
{
ThumbOpcode(0xe7fe);
}
// label: BX LR
ThumbOpcode(0x4770);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
}
[Test]
public void TestT32B2()
{
// BNE label1
ThumbOpcode(0xf040);
ThumbOpcode(0x8242);
// label2: BNE label3
ThumbOpcode(0xf040);
ThumbOpcode(0x8242);
for (int i = 0; i < 576; i++)
{
ThumbOpcode(0xe7fe);
}
// label1: BNE label2
ThumbOpcode(0xf47f);
ThumbOpcode(0xadbc);
// label3: BX LR
ThumbOpcode(0x4770);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
}
[Test]
public void TestT32B3()
{
// B.W label
ThumbOpcode(0xf000);
ThumbOpcode(0xba40);
for (int i = 0; i < 576; i++)
{
ThumbOpcode(0xe7fe);
}
// label: BX LR
ThumbOpcode(0x4770);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
}
[Test]
public void TestT32B4()
{
// B.W label1
ThumbOpcode(0xf000);
ThumbOpcode(0xba42);
// label2: B.W label3
ThumbOpcode(0xf000);
ThumbOpcode(0xba42);
for (int i = 0; i < 576; i++)
{
ThumbOpcode(0xe7fe);
}
// label1: B.W label2
ThumbOpcode(0xf7ff);
ThumbOpcode(0xbdbc);
// label3: BX LR
ThumbOpcode(0x4770);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
}
[Test]
public void TestT32Bl()
{
// BL label
ThumbOpcode(0xf000);
ThumbOpcode(0xf840);
for (int i = 0; i < 64; i++)
{
ThumbOpcode(0xe7fe);
}
ThumbOpcode(0x4670); // label: MOV R0, LR
ThumbOpcode(0x2100); // MOVS R1, #0
ThumbOpcode(0x468e); // MOV LR, R1
ThumbOpcode(0x4770); // BX LR
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005));
}
[Test]
public void TestT32Blx1()
{
// BLX label
ThumbOpcode(0xf000);
ThumbOpcode(0xe840);
for (int i = 0; i < 64; i++)
{
ThumbOpcode(0x4770);
}
// .arm ; label: MOV R0, LR
Opcode(0xe1a0000e);
// MOV LR, #0
Opcode(0xe3a0e000);
// BX LR
Opcode(0xe12fff1e);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1005));
Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false));
}
[Test]
public void TestT32Blx2()
{
// NOP
ThumbOpcode(0xbf00);
// BLX label
ThumbOpcode(0xf000);
ThumbOpcode(0xe840);
for (int i = 0; i < 63; i++)
{
ThumbOpcode(0x4770);
}
// .arm ; label: MOV R0, LR
Opcode(0xe1a0000e);
// MOV LR, #0
Opcode(0xe3a0e000);
// BX LR
Opcode(0xe12fff1e);
GetContext().SetPstateFlag(PState.TFlag, true);
ExecuteOpcodes(runUnicorn: false);
Assert.That(GetContext().GetX(0), Is.EqualTo(0x1007));
Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false));
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Tests.Cpu
{
public class PrecomputedThumbTestCase
{
public ushort[] Instructions;
public uint[] StartRegs;
public uint[] FinalRegs;
}
}

View File

@ -80,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
));

View File

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

View File

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

View File

@ -558,10 +558,10 @@ namespace Ryujinx.Ui.App
{
_ = 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();
}
@ -612,7 +612,7 @@ namespace Ryujinx.Ui.App
titleId = "0000000000000000";
}
version = controlData.DisplayVersion.ToString();
version = controlData.DisplayVersionString.ToString();
}
private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)

View File

@ -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.
@ -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();

View File

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

View File

@ -105,7 +105,7 @@ namespace Ryujinx.Ui.Windows
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
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);

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 255.76 255.76"><defs><style>.cls-1{fill:#02c5e5;}.cls-2{fill:#ff5f55;}.cls-3{fill:none;}</style></defs><g id="Ebene_2" data-name="Ebene 2"><g id="Ebene_1-2" data-name="Ebene 1"><g id="Ebene_2-2" data-name="Ebene 2"><g id="Ebene_1-2-2" data-name="Ebene 1-2"><path class="cls-1" d="M80.63,0V220.39H44.37c-14,0-35.74-20.74-35.74-39.13V40.13C8.63,19.19,31.36,0,49.06,0Z"/><path class="cls-2" d="M175.13,35.37V255.76h36.26c14,0,35.74-20.74,35.74-39.13V75.5c0-20.94-22.73-40.13-40.43-40.13Z"/><polygon class="cls-1" points="124.34 137.96 122.58 145.57 90.64 145.57 92.89 137.96 124.34 137.96"/><polygon class="cls-2" points="160.29 137.96 157.84 145.57 122.58 145.57 124.34 137.96 160.29 137.96"/><polygon class="cls-1" points="130.39 111.86 128.62 119.47 95.14 119.47 97.39 111.86 130.39 111.86"/><polygon class="cls-2" points="164.79 111.86 162.34 119.47 128.62 119.47 130.39 111.86 164.79 111.86"/><polygon class="cls-1" points="104.24 167.99 122.83 87.77 129.78 87.77 111.19 167.99 104.24 167.99"/><polygon class="cls-2" points="128.18 167.99 146.77 87.77 153.89 87.77 135.3 167.99 128.18 167.99"/></g><rect class="cls-3" width="255.76" height="255.76"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-nx-nca">
<comment>Nintendo Content Archive</comment>
<glob pattern="*.nca"/>
</mime-type>
<mime-type type="application/x-nx-nro">
<comment>Nintendo Relocatable Object</comment>
<glob pattern="*.nro"/>
</mime-type>
<mime-type type="application/x-nx-nso">
<comment>Nintendo Shared Object</comment>
<glob pattern="*.nso"/>
</mime-type>
<mime-type type="application/x-nx-nsp">
<comment>Nintendo Submission Package</comment>
<glob pattern="*.nsp"/>
</mime-type>
<mime-type type="application/x-nx-xci">
<comment>Nintendo Switch Cartridge</comment>
<glob pattern="*.xci"/>
</mime-type>
</mime-info>

View File

@ -0,0 +1,14 @@
[Desktop Entry]
Version=1.0
Name=Ryujinx
Comment=A Nintendo Switch Emulator
Type=Application
GenericName=Nintendo Switch Emulator
Icon=ryujinx
Terminal=false
Exec=Ryujinx %f
Categories=Game;Emulator;GTK;
MimeType=application/x-nx-nca;application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci;
Keywords=Switch;Nintendo;Emulator;
StartupWMClass=Ryujinx
PrefersNonDefaultGPU=true