Compare commits

...

11 Commits

Author SHA1 Message Date
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
c9c65af59e Perform unscaled 2d engine copy on CPU if source texture isn't in cache. (#3112)
* Initial implementation of fast 2d copy

TODO: Partial copy for mismatching region/size.

* WIP

* Cleanup

* Update Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2022-02-22 11:21:29 -03:00
dc063eac83 ARMeilleure: Implement single stepping (#3133)
* Decoder: Implement SingleInstruction decoder mode

* Translator: Implement Step

* DecoderMode: Rename Normal to MultipleBlocks
2022-02-22 11:11:42 -03:00
ccf23fc629 gui: Fixes the games icon when there is an update (#3148)
* gui: Fixes the games icon when there is a game update

Currently we just load the version of the update, instead of the whole NACP file. This PR fixes that. A little cleanup is made into the code to avoid duplicate things.
(Closes #3039)

* Fix condition
2022-02-22 14:53:39 +01:00
f1460d5494 A32: Fix BLX and BXWritePC (#3151) 2022-02-22 10:41:56 -03:00
644b497df1 Collapse AsSpan().Slice(..) calls into AsSpan(..) (#3145)
* Collapse AsSpan().Slice(..) calls into AsSpan(..)

Less code and a bit faster

* Collapse an Array.Clear(array, 0, array.Length) call to Array.Clear(array)
2022-02-22 10:32:10 -03:00
fb935fd201 Add dedicated ServerBase for FileSystem services (#3142)
This should prevent filesystem services from blocking other services that don't have their own ServerBase. May improve filesystem related stutters in certain titles.

Improves button advanced cutscenes such as Miqol's Request in Xenoblade: DE when the game is on a network share (used to stutter when voice lines played).

Should probably be tested to make sure no mysterious bugs have been unearthed, and to see if any other filesystem related perf issues are improved.
2022-02-19 15:29:11 +01:00
63 changed files with 1392 additions and 367 deletions

View File

@ -18,7 +18,7 @@ namespace ARMeilleure.Decoders
// For lower code quality translation, we set a lower limit since we're blocking execution.
private const int MaxInstsPerFunctionLowCq = 500;
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, bool singleBlock)
public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, DecoderMode dMode)
{
List<Block> blocks = new List<Block>();
@ -38,7 +38,7 @@ namespace ARMeilleure.Decoders
{
block = new Block(blkAddress);
if ((singleBlock && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
{
block.Exit = true;
block.EndAddress = blkAddress;
@ -96,6 +96,12 @@ namespace ARMeilleure.Decoders
}
}
if (dMode == DecoderMode.SingleInstruction)
{
// Only read at most one instruction
limitAddress = currBlock.Address + 1;
}
FillBlock(memory, mode, currBlock, limitAddress);
opsCount += currBlock.OpCodes.Count;
@ -143,7 +149,7 @@ namespace ARMeilleure.Decoders
throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}.");
}
if (!singleBlock)
if (dMode == DecoderMode.MultipleBlocks)
{
return TailCallRemover.RunPass(address, blocks);
}
@ -257,6 +263,11 @@ namespace ARMeilleure.Decoders
// so we must consider such operations as a branch in potential aswell.
if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc)
{
if (opCode is OpCodeT32)
{
return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq &&
opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn;
}
return true;
}

View File

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

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

@ -1,6 +1,7 @@
using ARMeilleure.Instructions;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace ARMeilleure.Decoders
{
@ -972,8 +973,7 @@ namespace ARMeilleure.Decoders
SetA32("111100111x11<<10xxxx00011xx0xxxx", InstName.Vzip, InstEmit32.Vzip, OpCode32SimdCmpZ.Create);
#endregion
#region "OpCode Table (AArch32, T16/T32)"
// T16
#region "OpCode Table (AArch32, T16)"
SetT16("000<<xxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT16ShiftImm.Create);
SetT16("0001100xxxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT16AddSubReg.Create);
SetT16("0001101xxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT16AddSubReg.Create);
@ -1045,6 +1045,26 @@ namespace ARMeilleure.Decoders
SetT16("11100xxxxxxxxxxx", InstName.B, InstEmit32.B, OpCodeT16BImm11.Create);
#endregion
#region "OpCode Table (AArch32, T32)"
// Base
SetT32("11101011010xxxxx0xxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCodeT32AluRsImm.Create);
SetT32("11101011000<xxxx0xxx<<<<xxxxxxxx", InstName.Add, InstEmit32.Add, OpCodeT32AluRsImm.Create);
SetT32("11101010000<xxxx0xxx<<<<xxxxxxxx", InstName.And, InstEmit32.And, OpCodeT32AluRsImm.Create);
SetT32("11101010001xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCodeT32AluRsImm.Create);
SetT32("111010110001xxxx0xxx1111xxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCodeT32AluRsImm.Create);
SetT32("111010111011xxxx0xxx1111xxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCodeT32AluRsImm.Create);
SetT32("11101010100<xxxx0xxx<<<<xxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCodeT32AluRsImm.Create);
SetT32("11101010010x11110xxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCodeT32AluRsImm.Create);
SetT32("11101010011x11110xxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCodeT32AluRsImm.Create);
SetT32("11101010011x<<<<0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit32.Orn, OpCodeT32AluRsImm.Create);
SetT32("11101010010x<<<<0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCodeT32AluRsImm.Create);
SetT32("11101011110xxxxx0xxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCodeT32AluRsImm.Create);
SetT32("11101011011xxxxx0xxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCodeT32AluRsImm.Create);
SetT32("11101011101<xxxx0xxx<<<<xxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCodeT32AluRsImm.Create);
SetT32("111010101001xxxx0xxx1111xxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCodeT32AluRsImm.Create);
SetT32("111010100001xxxx0xxx1111xxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCodeT32AluRsImm.Create);
#endregion
FillFastLookupTable(InstA32FastLookup, AllInstA32, ToFastLookupIndexA);
FillFastLookupTable(InstT32FastLookup, AllInstT32, ToFastLookupIndexT);
FillFastLookupTable(InstA64FastLookup, AllInstA64, ToFastLookupIndexA);
@ -1092,8 +1112,11 @@ namespace ARMeilleure.Decoders
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
{
encoding = encoding.Substring(16) + encoding.Substring(0, 16);
Set(encoding, AllInstT32, new InstDescriptor(name, emitter), makeOp);
string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
MakeOp reversedMakeOp =
(InstDescriptor inst, ulong address, int opCode)
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
Set(reversedEncoding, AllInstT32, new InstDescriptor(name, emitter), reversedMakeOp);
}
private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)

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

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

View File

@ -12,7 +12,7 @@ namespace ARMeilleure.Instructions
{
public static bool IsThumb(OpCode op)
{
return op is OpCodeT16;
return op is OpCodeT16 || op is OpCodeT32;
}
public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type)
@ -186,7 +186,7 @@ namespace ARMeilleure.Instructions
SetFlag(context, PState.TFlag, mode);
Operand addr = context.ConditionalSelect(mode, pc, context.BitwiseAnd(pc, Const(~3)));
Operand addr = context.ConditionalSelect(mode, context.BitwiseAnd(pc, Const(~1)), context.BitwiseAnd(pc, Const(~3)));
InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn);
}

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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>
@ -194,6 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TwodTexture copyTexture,
ulong offset,
FormatInfo formatInfo,
bool shouldCreate,
bool preferScaling = true,
Size? sizeHint = null)
{
@ -234,6 +245,11 @@ namespace Ryujinx.Graphics.Gpu.Image
flags |= TextureSearchFlags.WithUpscale;
}
if (!shouldCreate)
{
flags |= TextureSearchFlags.NoCreate;
}
Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint);
texture?.SynchronizeMemory();
@ -480,15 +496,29 @@ namespace Ryujinx.Graphics.Gpu.Image
return texture;
}
else if (flags.HasFlag(TextureSearchFlags.NoCreate))
{
return null;
}
// Calculate texture sizes, used to find all overlapping textures.
SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize);
ulong size = (ulong)sizeInfo.TotalSize;
bool partiallyMapped = false;
if (range == null)
{
range = memoryManager.GetPhysicalRegions(info.GpuAddress, size);
for (int i = 0; i < range.Value.Count; i++)
{
if (range.Value.GetSubRange(i).Address == MemoryManager.PteUnmapped)
{
partiallyMapped = true;
break;
}
}
}
// Find view compatible matches.
@ -658,7 +688,7 @@ namespace Ryujinx.Graphics.Gpu.Image
else
{
bool dataOverlaps = texture.DataOverlaps(overlap, compatibility);
if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group))
{
incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility));
@ -774,6 +804,14 @@ namespace Ryujinx.Graphics.Gpu.Image
_textures.Add(texture);
}
if (partiallyMapped)
{
lock (_partiallyMappedTextures)
{
_partiallyMappedTextures.Add(texture);
}
}
ShrinkOverlapsBufferIfNeeded();
for (int i = 0; i < overlapsCount; i++)
@ -1069,6 +1107,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_textures.Remove(texture);
}
lock (_partiallyMappedTextures)
{
_partiallyMappedTextures.Remove(texture);
}
}
/// <summary>

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

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

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;
@ -154,14 +154,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Gets a writable region from GPU mapped memory.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="va">Start address of the range</param>
/// <param name="size">Size in bytes to be range</param>
/// <param name="tracked">True if write tracking is triggered on the span</param>
/// <returns>A writable region with the data at the specified memory location</returns>
public WritableRegion GetWritableRegion(ulong va, int size)
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{
if (IsContiguous(va, size))
{
return Physical.GetWritableRegion(Translate(va), size);
return Physical.GetWritableRegion(Translate(va), size, tracked);
}
else
{
@ -169,7 +170,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
GetSpan(va, size).CopyTo(memory.Span);
return new WritableRegion(this, va, memory);
return new WritableRegion(this, va, memory, tracked);
}
}
@ -339,7 +340,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <returns>Multi-range with the physical regions</returns>
/// <exception cref="InvalidMemoryRegionException">The memory region specified by <paramref name="va"/> and <paramref name="size"/> is not fully mapped</exception>
public MultiRange GetPhysicalRegions(ulong va, ulong size)
{
if (IsContiguous(va, (int)size))
@ -347,11 +347,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
return new MultiRange(Translate(va), size);
}
if (!IsMapped(va))
{
throw new InvalidMemoryRegionException($"The specified GPU virtual address 0x{va:X} is not mapped.");
}
ulong regionStart = Translate(va);
ulong regionSize = Math.Min(size, PageSize - (va & PageMask));
@ -366,14 +361,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int page = 0; page < pages - 1; page++)
{
if (!IsMapped(va + PageSize))
{
throw new InvalidMemoryRegionException($"The specified GPU virtual memory range 0x{va:X}..0x{(va + size):X} is not fully mapped.");
}
ulong currPa = Translate(va);
ulong newPa = Translate(va + PageSize);
if (Translate(va) + PageSize != newPa)
if ((currPa != PteUnmapped || newPa != PteUnmapped) && currPa + PageSize != newPa)
{
regions.Add(new MemoryRange(regionStart, regionSize));
regionStart = newPa;
@ -404,18 +395,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
MemoryRange currentRange = range.GetSubRange(i);
ulong address = currentRange.Address & ~PageMask;
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
while (address < endAddress)
if (currentRange.Address != PteUnmapped)
{
if (Translate(va) != address)
{
return false;
}
ulong address = currentRange.Address & ~PageMask;
ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask;
va += PageSize;
address += PageSize;
while (address < endAddress)
{
if (Translate(va) != address)
{
return false;
}
va += PageSize;
address += PageSize;
}
}
else
{
ulong endVa = va + (((currentRange.Size) + PageMask) & ~PageMask);
while (va < endVa)
{
if (Translate(va) != PteUnmapped)
{
return false;
}
va += PageSize;
}
}
}

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

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

View File

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

View File

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

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

@ -76,6 +76,7 @@ namespace Ryujinx.HLE.HOS
internal ServerBase BsdServer { get; private set; }
internal ServerBase AudRenServer { get; private set; }
internal ServerBase AudOutServer { get; private set; }
internal ServerBase FsServer { get; private set; }
internal ServerBase HidServer { get; private set; }
internal ServerBase NvDrvServer { get; private set; }
internal ServerBase TimeServer { get; private set; }
@ -298,6 +299,7 @@ namespace Ryujinx.HLE.HOS
BsdServer = new ServerBase(KernelContext, "BsdServer");
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
FsServer = new ServerBase(KernelContext, "FsServer");
HidServer = new ServerBase(KernelContext, "HidServer");
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
TimeServer = new ServerBase(KernelContext, "TimeServer");
@ -464,7 +466,7 @@ namespace Ryujinx.HLE.HOS
AudioRendererManager.Dispose();
LibHacHorizonManager.AmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value);
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
KernelContext.Dispose();
}

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

@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Services
{
private int _disposeState;
public DisposableIpcService(ServerBase server = null) : base(server) { }
protected abstract void Dispose(bool isDisposing);
public void Dispose()

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

@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
{
private SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> _baseFileSystemProxy;
public IFileSystemProxy(ServiceCtx context)
public IFileSystemProxy(ServiceCtx context) : base(context.Device.System.FsServer)
{
var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient;
_baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject();

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

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Fs;
using LibHac.Loader;
using LibHac.Tools.FsSystem;
@ -12,9 +12,9 @@ namespace Ryujinx.HLE.Loaders.Executables
class NsoExecutable : IExecutable
{
public byte[] Program { get; }
public Span<byte> Text => Program.AsSpan().Slice((int)TextOffset, (int)TextSize);
public Span<byte> Ro => Program.AsSpan().Slice((int)RoOffset, (int)RoSize);
public Span<byte> Data => Program.AsSpan().Slice((int)DataOffset, (int)DataSize);
public Span<byte> Text => Program.AsSpan((int)TextOffset, (int)TextSize);
public Span<byte> Ro => Program.AsSpan((int)RoOffset, (int)RoSize);
public Span<byte> Data => Program.AsSpan((int)DataOffset, (int)DataSize);
public uint TextOffset { get; }
public uint RoOffset { get; }
@ -26,8 +26,8 @@ namespace Ryujinx.HLE.Loaders.Executables
public uint DataSize { get; }
public uint BssSize { get; }
public string Name;
public Buffer32 BuildId;
public string Name;
public Array32<byte> BuildId;
public NsoExecutable(IStorage inStorage, string name = null)
{
@ -58,7 +58,7 @@ namespace Ryujinx.HLE.Loaders.Executables
{
reader.GetSegmentSize(segmentType, out uint uncompressedSize).ThrowIfFailure();
var span = Program.AsSpan().Slice((int)offset, (int)uncompressedSize);
var span = Program.AsSpan((int)offset, (int)uncompressedSize);
reader.ReadSegment(segmentType, span).ThrowIfFailure();

View File

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

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

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

@ -242,15 +242,18 @@ namespace Ryujinx.Ui.App
}
else
{
// Store the ControlFS in variable called controlFs
GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId);
// Check if there is an update available.
if (IsUpdateApplied(titleId, out IFileSystem updatedControlFs))
{
// Replace the original ControlFs by the updated one.
controlFs = updatedControlFs;
}
ReadControlData(controlFs, controlHolder.ByteSpan);
// Get the title name, title ID, developer name and version number from the NACP
version = IsUpdateApplied(titleId, out string updateVersion) ? updateVersion : controlHolder.Value.DisplayVersion.ToString();
GetNameIdDeveloper(ref controlHolder.Value, out titleName, out _, out developer);
GetGameInformation(ref controlHolder.Value, out titleName, out _, out developer, out version);
// Read the icon from the ControlFS and store it as a byte array
try
@ -351,10 +354,7 @@ namespace Ryujinx.Ui.App
// Read the NACP data
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
// Get the title name, title ID, developer name and version number from the NACP
version = controlHolder.Value.DisplayVersion.ToString();
GetNameIdDeveloper(ref controlHolder.Value, out titleName, out titleId, out developer);
GetGameInformation(ref controlHolder.Value, out titleName, out titleId, out developer, out version);
}
else
{
@ -554,14 +554,14 @@ namespace Ryujinx.Ui.App
return readableString;
}
private void GetNameIdDeveloper(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher)
private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
{
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
if (controlData.Titles.Length > (int)desiredTitleLanguage)
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
{
titleName = controlData.Titles[(int)desiredTitleLanguage].Name.ToString();
publisher = controlData.Titles[(int)desiredTitleLanguage].Publisher.ToString();
titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
}
else
{
@ -571,11 +571,11 @@ namespace Ryujinx.Ui.App
if (string.IsNullOrWhiteSpace(titleName))
{
foreach (ApplicationControlTitle controlTitle in controlData.Titles)
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!((U8Span)controlTitle.Name).IsEmpty())
if (!controlTitle.NameString.IsEmpty())
{
titleName = controlTitle.Name.ToString();
titleName = controlTitle.NameString.ToString();
break;
}
@ -584,11 +584,11 @@ namespace Ryujinx.Ui.App
if (string.IsNullOrWhiteSpace(publisher))
{
foreach (ApplicationControlTitle controlTitle in controlData.Titles)
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!((U8Span)controlTitle.Publisher).IsEmpty())
if (!controlTitle.PublisherString.IsEmpty())
{
publisher = controlTitle.Publisher.ToString();
publisher = controlTitle.PublisherString.ToString();
break;
}
@ -599,7 +599,7 @@ namespace Ryujinx.Ui.App
{
titleId = controlData.PresenceGroupId.ToString("x16");
}
else if (controlData.SaveDataOwnerId.Value != 0)
else if (controlData.SaveDataOwnerId != 0)
{
titleId = controlData.SaveDataOwnerId.ToString();
}
@ -611,10 +611,14 @@ namespace Ryujinx.Ui.App
{
titleId = "0000000000000000";
}
version = controlData.DisplayVersionString.ToString();
}
private bool IsUpdateApplied(string titleId, out string version)
private bool IsUpdateApplied(string titleId, out IFileSystem updatedControlFs)
{
updatedControlFs = null;
string updatePath = "(unknown)";
try
@ -623,14 +627,7 @@ namespace Ryujinx.Ui.App
if (patchNca != null && controlNca != null)
{
ApplicationControlProperty controlData = new ApplicationControlProperty();
using var nacpFile = new UniqueRef<IFile>();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
version = controlData.DisplayVersion.ToString();
updatedControlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
return true;
}
@ -645,8 +642,6 @@ namespace Ryujinx.Ui.App
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
}
version = "";
return false;
}
}

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