mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-10-08 04:38:53 +02:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user