single MR with all patches i did, compared to the 2.0.5 release:

See merge request kenji-nx/ryujinx!18
This commit is contained in:
BeZide
2026-01-20 00:10:38 -06:00
committed by KeatonTheBot
parent dc4a51a462
commit 2c1b7bc667
38 changed files with 7401 additions and 598 deletions
@@ -293,6 +293,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
KThread currentThread = KernelStatic.GetCurrentThread();
KThread selectedThread = _state.SelectedThread;
// TODO fix building error CS1061
//if (!currentThread.IsThreadNamed && currentThread.GetThreadName() != "")
//{
// currentThread.HostThread.Name = $"<{currentThread.GetThreadName()}>";
// currentThread.IsThreadNamed = true;
//}
// If the thread is already scheduled and running on the core, we have nothing to do.
if (currentThread == selectedThread)
{
@@ -7,6 +7,9 @@ using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
using Ryujinx.Horizon.Common;
using Ryujinx.HLE.Kenjinx;
using System.Security.Cryptography;
using Ryujinx.Common.Logging;
using System;
using System.Buffers.Binary;
using System.Globalization;
@@ -142,7 +145,28 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
{
context.Device.System.NfpDevices[i].State = NfpDeviceState.SearchingForTag;
// ▼ If a tag has already been injected via Bridge, use it immediately.
if (KenjinxAmiiboShim.TryConsume(out var tagBytes))
{
var dev = context.Device.System.NfpDevices[i];
if (TryGetFigureIdFrom1Dc(tagBytes, out var fid, out var raw))
{
dev.AmiiboId = fid;
Logger.Info?.PrintMsg(LogClass.ServiceNfp, $"Kenjinx: raw@1DC={raw} → FigureID={fid}");
}
else
{
dev.AmiiboId = MakeAmiiboIdFromBytes(tagBytes); // Fallback (SHA1)
Logger.Info?.PrintMsg(LogClass.ServiceNfp, $"Kenjinx: FigureID={dev.AmiiboId} (fallback, keine 1DC-Bytes)");
}
dev.UseRandomUuid = false; // stabile UUID über die Session
dev.State = NfpDeviceState.TagFound;
}
// ▲
break;
}
}
_cancelTokenSource = new CancellationTokenSource();
@@ -155,7 +179,32 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
break;
}
// ▼ Poll during the search for newly injected tags.
for (int d = 0; d < context.Device.System.NfpDevices.Count; d++)
{
var dev = context.Device.System.NfpDevices[d];
if (dev.State == NfpDeviceState.SearchingForTag && KenjinxAmiiboShim.TryConsume(out var tagBytesLoop))
{
if (TryGetFigureIdFrom1Dc(tagBytesLoop, out var fid, out var raw))
{
dev.AmiiboId = fid;
Logger.Info?.PrintMsg(LogClass.ServiceNfp, $"Kenjinx: raw@1DC={raw} → FigureID={fid}");
}
else
{
dev.AmiiboId = MakeAmiiboIdFromBytes(tagBytesLoop); // Fallback (SHA1)
Logger.Info?.PrintMsg(LogClass.ServiceNfp, $"Kenjinx: FigureID={dev.AmiiboId} (fallback, keine 1DC-Bytes)");
}
dev.UseRandomUuid = false;
dev.State = NfpDeviceState.TagFound;
}
}
// ▲
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
{
@@ -196,9 +245,62 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
break;
}
}
// ▼ Discard the injected day
KenjinxAmiiboShim.Clear();
// ▲
return ResultCode.Success;
}
// ▼▼▼ FigureID directly from 0x1DC (8 bytes) without 16-bit swapping
private static bool TryGetFigureIdFrom1Dc(byte[] data, out string figureId, out string debugRaw)
{
figureId = string.Empty;
debugRaw = string.Empty;
const int off = 0x1DC;
const int len = 8;
if (data == null || data.Length < off + len)
{
return false;
}
var s = data.AsSpan(off, len);
// Debug string "AA-BB-..." in uppercase letters
debugRaw = $"{s[0]:X2}-{s[1]:X2}-{s[2]:X2}-{s[3]:X2}-{s[4]:X2}-{s[5]:X2}-{s[6]:X2}-{s[7]:X2}";
// FigureID as a 16-digit lowercase hexadecimal string in the **same order**
// Layout: [0..1]=CharId(BE), [2]=Variant, [3]=Type, [4..5]=Model(BE), [6]=Series, [7]=unknown
figureId =
$"{s[0]:x2}{s[1]:x2}{s[2]:x2}{s[3]:x2}{s[4]:x2}{s[5]:x2}{s[6]:x2}{s[7]:x2}";
return true;
}
// ▲▲▲
// Fallback: Deterministic 16 hexadecimal characters from SHA1 (first 8 bytes)
private static string MakeAmiiboIdFromBytes(byte[] data)
{
if (data == null || data.Length == 0) return "0000000000000000";
using var sha = SHA1.Create();
var hash = sha.ComputeHash(data); // 20 Bytes
char[] buf = new char[16];
for (int i = 0, k = 0; i < 8; i++)
{
byte b = hash[i];
buf[k++] = GetHex((b >> 4) & 0xF);
buf[k++] = GetHex(b & 0xF);
}
return new string(buf);
}
private static char GetHex(int v) => (char)(v < 10 ? ('0' + v) : ('a' + (v - 10)));
[CommandCmif(5)]
// Mount(bytes<8, 4>, u32, u32)
public ResultCode Mount(ServiceCtx context)
@@ -0,0 +1,51 @@
#nullable enable
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
{
/// <summary>
/// A small static buffer + an API that we access from the Android side via reflection.
/// </summary>
public static class KenjinxAmiiboShim
{
private static byte[]? s_tag;
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool InjectAmiibo(byte[] tagBytes)
{
if (tagBytes is null || tagBytes.Length == 0) return false;
s_tag = (byte[])tagBytes.Clone();
System.Diagnostics.Debug.WriteLine($"[Kenjinx] KenjinxAmiiboShim.InjectAmiibo bytes={tagBytes.Length}");
return true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ClearAmiibo()
{
s_tag = null;
System.Diagnostics.Debug.WriteLine("[Kenjinx] KenjinxAmiiboShim.ClearAmiibo");
}
// ▼ Used by INfp – retrieves the data exactly once a day and empties the buffer
public static bool TryConsume(out byte[] data)
{
if (s_tag is null)
{
data = Array.Empty<byte>();
return false;
}
data = s_tag;
s_tag = null;
return true;
}
// ▼ Convenient alias
public static void Clear() => ClearAmiibo();
public static bool HasInjectedAmiibo => s_tag is not null;
public static ReadOnlySpan<byte> PeekInjectedAmiibo()
=> s_tag is null ? ReadOnlySpan<byte>.Empty : new ReadOnlySpan<byte>(s_tag);
}
}
+157
View File
@@ -0,0 +1,157 @@
#nullable enable
using System;
using System.Reflection;
using Ryujinx.HLE;
namespace Ryujinx.HLE.Kenjinx
{
public static class AmiiboBridge
{
public static bool TryLoadVirtualAmiibo(Switch device, byte[] data, out string message)
{
message = "NFP bridge not wired.";
if (device?.System is null)
{
message = "No System on Switch device.";
return false;
}
// --- TRY 0: Our conflict-free shim class first (recommended) ---
// Type name including assembly: Ryujinx.HLE
var shimType = Type.GetType(
"Ryujinx.HLE.HOS.Services.Nfc.Nfp.KenjinxAmiiboShim, Ryujinx.HLE",
throwOnError: false
);
if (shimType != null)
{
var m = shimType.GetMethod(
"InjectAmiibo",
BindingFlags.Public | BindingFlags.Static,
binder: null,
types: new[] { typeof(byte[]) },
modifiers: null
);
if (m != null)
{
var ok = (bool)(m.Invoke(null, new object[] { data }) ?? false);
if (ok)
{
message = "Injected via KenjinxAmiiboShim.InjectAmiibo";
return true;
}
}
}
// --- TRY 0.5: If available – static API directly on NfpManager ---
// (Only if you later add a static InjectAmiibo(...) method to NfpManager.)
var nfpManagerType = Type.GetType(
"Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager, Ryujinx.HLE",
throwOnError: false
);
if (nfpManagerType != null)
{
var m = nfpManagerType.GetMethod(
"InjectAmiibo",
BindingFlags.Public | BindingFlags.Static,
binder: null,
types: new[] { typeof(byte[]) },
modifiers: null
);
if (m != null)
{
var ok = m.Invoke(null, new object[] { data });
message = "Injected via NfpManager.InjectAmiibo(static)";
return true;
}
}
// --- TRY 1: Direct access to an existing manager in the system object (your existing code) ---
var sys = device.System;
var nfpMgr =
GetField(sys, "NfpManager") ??
GetField(sys, "_nfpManager") ??
GetField(sys, "NfcManager") ??
GetField(sys, "_nfcManager");
if (nfpMgr != null)
{
// Possible method identifiers depending on the tree:
var mi =
GetMethod(nfpMgr, "InjectAmiibo", new[] { typeof(byte[]) }) ??
GetMethod(nfpMgr, "LoadAmiiboFromBytes", new[] { typeof(byte[]) }) ??
GetMethod(nfpMgr, "LoadVirtualAmiibo", new[] { typeof(byte[]) }) ??
GetMethod(nfpMgr, "ScanAmiiboFromBuffer", new[] { typeof(byte[]) });
if (mi != null)
{
mi.Invoke(nfpMgr, new object[] { data });
message = $"Injected via {mi.DeclaringType?.Name}.{mi.Name}";
return true;
}
}
message = "No NFP manager API found. Add a fixed InjectAmiibo(byte[]) to your NFP layer (see TODO in AmiiboBridge comments).";
return false;
}
public static void ClearVirtualAmiibo(Switch device)
{
if (device?.System is null) return;
// --- Clear via Shim (if present) ---
var shimType = Type.GetType(
"Ryujinx.HLE.HOS.Services.Nfc.Nfp.KenjinxAmiiboShim, Ryujinx.HLE",
throwOnError: false
);
shimType?
.GetMethod("ClearAmiibo", BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null)
?.Invoke(null, null);
// --- Optional: Static clear API on NfpManager (if available) ---
var nfpManagerType = Type.GetType(
"Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager, Ryujinx.HLE",
throwOnError: false
);
nfpManagerType?
.GetMethod("ClearAmiibo", BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null)
?.Invoke(null, null);
// --- Your existing instance fallback ---
var sys = device.System;
var nfpMgr =
GetField(sys, "NfpManager") ??
GetField(sys, "_nfpManager") ??
GetField(sys, "NfcManager") ??
GetField(sys, "_nfcManager");
if (nfpMgr != null)
{
var mi =
GetMethod(nfpMgr, "ClearAmiibo", Type.EmptyTypes) ??
GetMethod(nfpMgr, "ResetVirtualAmiibo", Type.EmptyTypes);
mi?.Invoke(nfpMgr, Array.Empty<object>());
}
}
private static object? GetField(object target, string name)
{
var t = target.GetType();
var f = t.GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
return f?.GetValue(target);
}
private static MethodInfo? GetMethod(object target, string name, Type[]? sig)
{
var t = target.GetType();
return t.GetMethod(
name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy,
binder: null,
types: sig ?? Type.EmptyTypes,
modifiers: null);
}
}
}