mirror of
https://github.com/GreemDev/Ryujinx
synced 2025-01-08 20:16:55 +01:00
First attempt
Currently DateTime is incorrect, Still trying to figure out application data.
This commit is contained in:
parent
08b7257be5
commit
6b155f5fbe
10 changed files with 469 additions and 3 deletions
|
@ -16,6 +16,7 @@ using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemA
|
|||
using Ryujinx.HLE.HOS.Services.Apm;
|
||||
using Ryujinx.HLE.HOS.Services.Caps;
|
||||
using Ryujinx.HLE.HOS.Services.Mii;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Bin;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
using Ryujinx.HLE.HOS.Services.Nv;
|
||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
|
||||
|
@ -344,6 +345,17 @@ namespace Ryujinx.HLE.HOS
|
|||
NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid;
|
||||
}
|
||||
}
|
||||
public void ScanAmiiboFromBin(string path)
|
||||
{
|
||||
byte[] encryptedData = File.ReadAllBytes(path);
|
||||
VirtualAmiiboFile newFile = AmiiboBinReader.ReadBinFile(encryptedData);
|
||||
if (SearchingForAmiibo(out int nfpDeviceId))
|
||||
{
|
||||
NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound;
|
||||
NfpDevices[nfpDeviceId].AmiiboId = newFile.AmiiboId;
|
||||
NfpDevices[nfpDeviceId].UseRandomUuid = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SearchingForAmiibo(out int nfpDeviceId)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
using Ryujinx.HLE.HOS.Tamper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Bin
|
||||
{
|
||||
public class AmiiboBinReader
|
||||
{
|
||||
// Method to calculate BCC (XOR checksum) from UID bytes
|
||||
private static byte CalculateBCC(byte[] uid, int startIdx)
|
||||
{
|
||||
return (byte)(uid[startIdx] ^ uid[startIdx + 1] ^ uid[startIdx + 2] ^ 0x88);
|
||||
}
|
||||
|
||||
// Method to read and process a .bin file
|
||||
public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes)
|
||||
{
|
||||
string keyRetailBinPath = GetKeyRetailBinPath();
|
||||
if (string.IsNullOrEmpty(keyRetailBinPath))
|
||||
{
|
||||
Console.WriteLine("Key retail bin path not found.");
|
||||
return new VirtualAmiiboFile();
|
||||
}
|
||||
|
||||
byte[] initialCounter = new byte[16];
|
||||
|
||||
// Ensure the file is long enough
|
||||
if (fileBytes.Length < 128 * 4) // Each page is 4 bytes, total 512 bytes
|
||||
{
|
||||
Console.WriteLine("File is too short to process.");
|
||||
return new VirtualAmiiboFile();
|
||||
}
|
||||
|
||||
// Decrypt the Amiibo data
|
||||
AmiiboDecrypter amiiboDecryptor = new AmiiboDecrypter(keyRetailBinPath);
|
||||
byte[] decryptedFileBytes = amiiboDecryptor.DecryptAmiiboData(fileBytes, initialCounter);
|
||||
|
||||
// Assuming the UID is stored in the first 7 bytes (NTAG215 UID length)
|
||||
byte[] uid = new byte[7];
|
||||
Array.Copy(fileBytes, 0, uid, 0, 7);
|
||||
|
||||
// Calculate BCC values
|
||||
byte bcc0 = CalculateBCC(uid, 0); // BCC0 = UID0 ^ UID1 ^ UID2 ^ 0x88
|
||||
byte bcc1 = CalculateBCC(uid, 3); // BCC1 = UID3 ^ UID4 ^ UID5 ^ 0x00
|
||||
|
||||
Console.WriteLine($"UID: {BitConverter.ToString(uid)}");
|
||||
Console.WriteLine($"BCC0: 0x{bcc0:X2}, BCC1: 0x{bcc1:X2}");
|
||||
|
||||
// Initialize byte arrays for data extraction
|
||||
byte[] nickNameBytes = new byte[20]; // Amiibo nickname is 20 bytes
|
||||
byte[] titleId = new byte[8];
|
||||
byte[] usedCharacter = new byte[2];
|
||||
byte[] variation = new byte[2];
|
||||
byte[] amiiboID = new byte[2];
|
||||
byte[] setID = new byte[1];
|
||||
byte[] initDate = new byte[2];
|
||||
byte[] writeDate = new byte[2];
|
||||
byte[] writeCounter = new byte[2];
|
||||
byte formData = 0;
|
||||
byte[] applicationAreas = new byte[212];
|
||||
|
||||
// Reading specific pages and parsing bytes
|
||||
for (int page = 0; page < 128; page++) // NTAG215 has 128 pages
|
||||
{
|
||||
int pageStartIdx = page * 4; // Each page is 4 bytes
|
||||
byte[] pageData = new byte[4];
|
||||
bool isEncrypted = IsPageEncrypted(page);
|
||||
byte[] sourceBytes = isEncrypted ? decryptedFileBytes : fileBytes;
|
||||
Array.Copy(sourceBytes, pageStartIdx, pageData, 0, 4);
|
||||
|
||||
// Special handling for specific pages
|
||||
switch (page)
|
||||
{
|
||||
case 0: // Page 0 (UID + BCC0)
|
||||
Console.WriteLine("Page 0: UID and BCC0.");
|
||||
break;
|
||||
|
||||
case 2: // Page 2 (BCC1 + Internal Value)
|
||||
byte internalValue = pageData[1];
|
||||
Console.WriteLine($"Page 2: BCC1 + Internal Value 0x{internalValue:X2} (Expected 0x48).");
|
||||
break;
|
||||
|
||||
case 6:
|
||||
// Bytes 0 and 1 are init date, bytes 2 and 3 are write date
|
||||
Array.Copy(pageData, 0, initDate, 0, 2);
|
||||
Array.Copy(pageData, 2, writeDate, 0, 2);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
// Extract nickname bytes
|
||||
int nickNameOffset = (page - 8) * 4;
|
||||
Array.Copy(pageData, 0, nickNameBytes, nickNameOffset, 4);
|
||||
break;
|
||||
|
||||
case 21:
|
||||
// Bytes 0 and 1 are used character, bytes 2 and 3 are variation
|
||||
Array.Copy(pageData, 0, usedCharacter, 0, 2);
|
||||
Array.Copy(pageData, 2, variation, 0, 2);
|
||||
break;
|
||||
|
||||
case 22:
|
||||
// Bytes 0 and 1 are amiibo ID, byte 2 is set ID, byte 3 is form data
|
||||
Array.Copy(pageData, 0, amiiboID, 0, 2);
|
||||
setID[0] = pageData[2];
|
||||
formData = pageData[3];
|
||||
break;
|
||||
|
||||
case 64:
|
||||
case 65:
|
||||
// Extract title ID
|
||||
int titleIdOffset = (page - 64) * 4;
|
||||
Array.Copy(pageData, 0, titleId, titleIdOffset, 4);
|
||||
break;
|
||||
|
||||
case 66:
|
||||
// Bytes 0 and 1 are write counter
|
||||
Array.Copy(pageData, 0, writeCounter, 0, 2);
|
||||
break;
|
||||
|
||||
// Pages 76 to 127 are application areas
|
||||
case >= 76 and <= 127:
|
||||
int appAreaOffset = (page - 76) * 4;
|
||||
Array.Copy(pageData, 0, applicationAreas, appAreaOffset, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Debugging
|
||||
string titleIdStr = BitConverter.ToString(titleId).Replace("-", "");
|
||||
string usedCharacterStr = BitConverter.ToString(usedCharacter).Replace("-", "");
|
||||
string variationStr = BitConverter.ToString(variation).Replace("-", "");
|
||||
string amiiboIDStr = BitConverter.ToString(amiiboID).Replace("-", "");
|
||||
string formDataStr = formData.ToString("X2");
|
||||
string setIDStr = BitConverter.ToString(setID).Replace("-", "");
|
||||
string nickName = Encoding.BigEndianUnicode.GetString(nickNameBytes).TrimEnd('\0');
|
||||
string head = usedCharacterStr + variationStr;
|
||||
string tail = amiiboIDStr + setIDStr + "02";
|
||||
string finalID = head + tail;
|
||||
string initDateStr = BitConverter.ToString(initDate).Replace("-", "");
|
||||
string writeDateStr = BitConverter.ToString(writeDate).Replace("-", "");
|
||||
|
||||
Console.WriteLine($"Title ID: {titleIdStr}");
|
||||
Console.WriteLine($"Head: {head}");
|
||||
Console.WriteLine($"Tail: {tail}");
|
||||
Console.WriteLine($"Used Character: {usedCharacterStr}");
|
||||
Console.WriteLine($"Form Data: {formDataStr}");
|
||||
Console.WriteLine($"Variation: {variationStr}");
|
||||
Console.WriteLine($"Amiibo ID: {amiiboIDStr}");
|
||||
Console.WriteLine($"Set ID: {setIDStr}");
|
||||
Console.WriteLine($"Final ID: {finalID}");
|
||||
Console.WriteLine($"Nickname: {nickName}");
|
||||
Console.WriteLine($"Init Date: {initDateStr}");
|
||||
Console.WriteLine($"Write Date: {writeDateStr}");
|
||||
|
||||
VirtualAmiiboFile virtualAmiiboFile = new VirtualAmiiboFile
|
||||
{
|
||||
FileVersion = 1,
|
||||
TagUuid = uid,
|
||||
AmiiboId = finalID
|
||||
};
|
||||
|
||||
DateTime initDateTime = DateTimeFromBytes(initDate);
|
||||
DateTime writeDateTime = DateTimeFromBytes(writeDate);
|
||||
|
||||
Console.WriteLine($"Parsed Init Date: {initDateTime}");
|
||||
Console.WriteLine($"Parsed Write Date: {writeDateTime}");
|
||||
|
||||
virtualAmiiboFile.FirstWriteDate = initDateTime;
|
||||
virtualAmiiboFile.LastWriteDate = writeDateTime;
|
||||
virtualAmiiboFile.WriteCounter = BitConverter.ToUInt16(writeCounter, 0);
|
||||
|
||||
// Parse application areas
|
||||
//List<VirtualAmiiboApplicationArea> applicationAreasList = ParseAmiiboData(applicationAreas);
|
||||
List<VirtualAmiiboApplicationArea> applicationAreasList = new List<VirtualAmiiboApplicationArea>();
|
||||
virtualAmiiboFile.ApplicationAreas = applicationAreasList;
|
||||
|
||||
// Save the virtual Amiibo file
|
||||
VirtualAmiibo.SaveAmiiboFile(virtualAmiiboFile);
|
||||
|
||||
return virtualAmiiboFile;
|
||||
}
|
||||
|
||||
private static string GetKeyRetailBinPath()
|
||||
{
|
||||
return Path.Combine(AppDataManager.KeysDirPath, "key_retail.bin");
|
||||
}
|
||||
|
||||
public static bool IsPageEncrypted(int page)
|
||||
{
|
||||
return (page >= 6 && page <= 9) || (page >= 43 && page <= 84);
|
||||
}
|
||||
|
||||
public static DateTime DateTimeFromBytes(byte[] date)
|
||||
{
|
||||
if (date == null || date.Length != 2)
|
||||
{
|
||||
Console.WriteLine("Invalid date bytes.");
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
|
||||
ushort value = BitConverter.ToUInt16(date, 0);
|
||||
|
||||
int day = value & 0x1F;
|
||||
int month = (value >> 5) & 0x0F;
|
||||
int year = (value >> 9) & 0x7F;
|
||||
|
||||
try
|
||||
{
|
||||
return new DateTime(2000 + year, month, day);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Console.WriteLine("Invalid date values extracted.");
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<VirtualAmiiboApplicationArea> ParseAmiiboData(byte[] decryptedData)
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<VirtualAmiiboApplicationArea>>(decryptedData);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.HLE.HOS.Services.Mii.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Bin
|
||||
{
|
||||
public class AmiiboDecrypter
|
||||
{
|
||||
public readonly byte[] _hmacKey; // HMAC key
|
||||
public readonly byte[] _aesKey; // AES key
|
||||
|
||||
public AmiiboDecrypter(string keyRetailBinPath)
|
||||
{
|
||||
var keys = AmiiboMasterKey.FromCombinedBin(File.ReadAllBytes(keyRetailBinPath));
|
||||
_hmacKey = keys.DataKey.HmacKey;
|
||||
_aesKey = keys.DataKey.XorPad;
|
||||
}
|
||||
|
||||
public byte[] DecryptAmiiboData(byte[] encryptedData, byte[] counter)
|
||||
{
|
||||
// Ensure the counter length matches the block size
|
||||
if (counter.Length != 16)
|
||||
{
|
||||
throw new ArgumentException("Counter must be 16 bytes long for AES block size.");
|
||||
}
|
||||
|
||||
byte[] decryptedData = new byte[encryptedData.Length];
|
||||
|
||||
using (Aes aesAlg = Aes.Create())
|
||||
{
|
||||
aesAlg.Key = _aesKey;
|
||||
aesAlg.Mode = CipherMode.ECB; // Use ECB mode to handle the counter encryption
|
||||
aesAlg.Padding = PaddingMode.None;
|
||||
|
||||
using (var encryptor = aesAlg.CreateEncryptor())
|
||||
{
|
||||
int blockSize = 16;
|
||||
byte[] encryptedCounter = new byte[blockSize];
|
||||
byte[] currentCounter = (byte[])counter.Clone();
|
||||
|
||||
for (int i = 0; i < encryptedData.Length; i += blockSize)
|
||||
{
|
||||
// Encrypt the current counter block
|
||||
encryptor.TransformBlock(currentCounter, 0, blockSize, encryptedCounter, 0);
|
||||
|
||||
// XOR the encrypted counter with the ciphertext to get the decrypted data
|
||||
for (int j = 0; j < blockSize && i + j < encryptedData.Length; j++)
|
||||
{
|
||||
decryptedData[i + j] = (byte)(encryptedData[i + j] ^ encryptedCounter[j]);
|
||||
}
|
||||
|
||||
// Increment the counter for the next block
|
||||
IncrementCounter(currentCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decryptedData;
|
||||
}
|
||||
|
||||
public byte[] CalculateHMAC(byte[] data)
|
||||
{
|
||||
using (var hmac = new HMACSHA256(_hmacKey))
|
||||
{
|
||||
return hmac.ComputeHash(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void IncrementCounter(byte[] counter)
|
||||
{
|
||||
for (int i = counter.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (++counter[i] != 0)
|
||||
break; // Stop if no overflow
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime ParseDate(byte[] data, int offset)
|
||||
{
|
||||
ushort year = BitConverter.ToUInt16(data, offset);
|
||||
byte month = data[offset + 2];
|
||||
byte day = data[offset + 3];
|
||||
byte hour = data[offset + 4];
|
||||
byte minute = data[offset + 5];
|
||||
byte second = data[offset + 6];
|
||||
|
||||
return new DateTime(year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
public List<object> ParseApplicationAreas(byte[] data, int startOffset, int areaSize)
|
||||
{
|
||||
var areas = new List<object>();
|
||||
for (int i = 0; i < 8; i++) // Assuming 8 areas
|
||||
{
|
||||
int offset = startOffset + (i * areaSize);
|
||||
string applicationId = BitConverter.ToString(data[offset..(offset + 4)]).Replace("-", "");
|
||||
byte[] areaData = data[(offset + 4)..(offset + areaSize)];
|
||||
areas.Add(new VirtualAmiiboApplicationArea
|
||||
{
|
||||
ApplicationAreaId = uint.Parse(applicationId),
|
||||
ApplicationArea = areaData
|
||||
});
|
||||
}
|
||||
|
||||
return areas;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Bin
|
||||
{
|
||||
public class AmiiboMasterKey
|
||||
{
|
||||
private const int DataLength = 80;
|
||||
private const int CombinedLength = 160;
|
||||
public byte[] HmacKey { get; private set; } // 16 bytes
|
||||
public byte[] TypeString { get; private set; } // 14 bytes
|
||||
public byte Rfu { get; private set; } // 1 byte reserved
|
||||
public byte MagicSize { get; private set; } // 1 byte
|
||||
public byte[] MagicBytes { get; private set; } // 16 bytes
|
||||
public byte[] XorPad { get; private set; } // 32 bytes
|
||||
|
||||
private AmiiboMasterKey(byte[] data)
|
||||
{
|
||||
if (data.Length != DataLength)
|
||||
throw new ArgumentException($"Data is {data.Length} bytes (should be {DataLength}).");
|
||||
|
||||
|
||||
// Unpack the data
|
||||
HmacKey = data[..16];
|
||||
TypeString = data[16..30];
|
||||
Rfu = data[30];
|
||||
MagicSize = data[31];
|
||||
MagicBytes = data[32..48];
|
||||
XorPad = data[48..];
|
||||
}
|
||||
|
||||
public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromSeparateBin(byte[] dataBin, byte[] tagBin)
|
||||
{
|
||||
var dataKey = new AmiiboMasterKey(dataBin);
|
||||
var tagKey = new AmiiboMasterKey(tagBin);
|
||||
return (dataKey, tagKey);
|
||||
}
|
||||
|
||||
public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromSeparateHex(string dataHex, string tagHex)
|
||||
{
|
||||
return FromSeparateBin(HexToBytes(dataHex), HexToBytes(tagHex));
|
||||
}
|
||||
|
||||
public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromCombinedBin(byte[] combinedBin)
|
||||
{
|
||||
if (combinedBin.Length != CombinedLength)
|
||||
throw new ArgumentException($"Data is {combinedBin.Length} bytes (should be {CombinedLength}).");
|
||||
|
||||
byte[] dataBin = combinedBin[..DataLength];
|
||||
byte[] tagBin = combinedBin[DataLength..];
|
||||
return FromSeparateBin(dataBin, tagBin);
|
||||
}
|
||||
|
||||
private static byte[] HexToBytes(string hex)
|
||||
{
|
||||
int length = hex.Length / 2;
|
||||
byte[] bytes = new byte[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
|
||||
{
|
||||
struct VirtualAmiiboFile
|
||||
public struct VirtualAmiiboFile
|
||||
{
|
||||
public uint FileVersion { get; set; }
|
||||
public byte[] TagUuid { get; set; }
|
||||
|
@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
|
|||
public List<VirtualAmiiboApplicationArea> ApplicationAreas { get; set; }
|
||||
}
|
||||
|
||||
struct VirtualAmiiboApplicationArea
|
||||
public struct VirtualAmiiboApplicationArea
|
||||
{
|
||||
public uint ApplicationAreaId { get; set; }
|
||||
public byte[] ApplicationArea { get; set; }
|
||||
|
|
|
@ -204,7 +204,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
return virtualAmiiboFile;
|
||||
}
|
||||
|
||||
private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile)
|
||||
public static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile)
|
||||
{
|
||||
string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json");
|
||||
JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, _serializerContext.VirtualAmiiboFile);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
"MenuBarActions": "_Actions",
|
||||
"MenuBarOptionsSimulateWakeUpMessage": "Simulate Wake-up message",
|
||||
"MenuBarActionsScanAmiibo": "Scan An Amiibo",
|
||||
"MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)",
|
||||
"MenuBarTools": "_Tools",
|
||||
"MenuBarToolsInstallFirmware": "Install Firmware",
|
||||
"MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP",
|
||||
|
|
|
@ -28,12 +28,14 @@ using Ryujinx.HLE;
|
|||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Bin;
|
||||
using Ryujinx.HLE.UI;
|
||||
using Ryujinx.Input.HLE;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
using Silk.NET.Vulkan;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -2059,6 +2061,32 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
}
|
||||
}
|
||||
}
|
||||
public async Task OpenBinFile()
|
||||
{
|
||||
if (!IsAmiiboRequested)
|
||||
return;
|
||||
|
||||
if (AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
||||
{
|
||||
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
{
|
||||
Patterns = new[] { "*.bin" },
|
||||
}
|
||||
}
|
||||
});
|
||||
if (result.Count > 0)
|
||||
{
|
||||
AppHost.Device.System.ScanAmiiboFromBin(result[0].Path.LocalPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void ToggleFullscreen()
|
||||
{
|
||||
|
|
|
@ -241,6 +241,13 @@
|
|||
Icon="{ext:Icon mdi-cube-scan}"
|
||||
InputGesture="Ctrl + A"
|
||||
IsEnabled="{Binding IsAmiiboRequested}" />
|
||||
<MenuItem
|
||||
Name="ScanAmiiboMenuItemFromBin"
|
||||
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
|
||||
Click="OpenBinFile"
|
||||
Header="{ext:Locale MenuBarActionsScanAmiiboBin}"
|
||||
Icon="{ext:Icon mdi-cube-scan}"
|
||||
IsEnabled="{Binding IsAmiiboRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding TakeScreenshot}"
|
||||
Header="{ext:Locale MenuBarFileToolsTakeScreenshot}"
|
||||
|
|
|
@ -141,6 +141,9 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||
public async void OpenAmiiboWindow(object sender, RoutedEventArgs e)
|
||||
=> await ViewModel.OpenAmiiboWindow();
|
||||
|
||||
public async void OpenBinFile(object sender, RoutedEventArgs e)
|
||||
=> await ViewModel.OpenBinFile();
|
||||
|
||||
public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!ViewModel.IsGameRunning)
|
||||
|
|
Loading…
Reference in a new issue