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