mirror of
https://github.com/GreemDev/Ryujinx
synced 2025-01-02 17:19:43 +01:00
Merge 3c96c341d2
into b05eab21a2
This commit is contained in:
commit
b2128f191c
5 changed files with 393 additions and 120 deletions
|
@ -339,10 +339,9 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid)
|
||||
{
|
||||
if (VirtualAmiibo.ApplicationBytes.Length > 0)
|
||||
if (VirtualAmiibo.VirtualAmiiboBinFile != null)
|
||||
{
|
||||
VirtualAmiibo.ApplicationBytes = Array.Empty<byte>();
|
||||
VirtualAmiibo.InputBin = string.Empty;
|
||||
VirtualAmiibo.VirtualAmiiboBinFile.SaveFile();
|
||||
}
|
||||
if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag)
|
||||
{
|
||||
|
@ -353,13 +352,7 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
public void ScanAmiiboFromBin(string path)
|
||||
{
|
||||
VirtualAmiibo.InputBin = path;
|
||||
if (VirtualAmiibo.ApplicationBytes.Length > 0)
|
||||
{
|
||||
VirtualAmiibo.ApplicationBytes = Array.Empty<byte>();
|
||||
}
|
||||
byte[] encryptedData = File.ReadAllBytes(path);
|
||||
VirtualAmiiboFile newFile = AmiiboBinReader.ReadBinFile(encryptedData);
|
||||
VirtualAmiiboFile newFile = AmiiboBinReader.ReadBinFile(path);
|
||||
if (SearchingForAmiibo(out int nfpDeviceId))
|
||||
{
|
||||
NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound;
|
||||
|
|
|
@ -19,8 +19,19 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
|||
return (byte)(uid[3] ^ uid[4] ^ uid[5] ^ uid[6]);
|
||||
}
|
||||
|
||||
public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes)
|
||||
public static VirtualAmiiboFile ReadBinFile(string filePath)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceNfp, "Reading bin file.");
|
||||
byte[] fileBytes;
|
||||
try
|
||||
{
|
||||
fileBytes = File.ReadAllBytes(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceNfp, $"Error reading file: {ex.Message}");
|
||||
return new VirtualAmiiboFile();
|
||||
}
|
||||
string keyRetailBinPath = GetKeyRetailBinPath();
|
||||
if (string.IsNullOrEmpty(keyRetailBinPath))
|
||||
{
|
||||
|
@ -39,6 +50,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
|||
byte[] newFileBytes = new byte[totalBytes];
|
||||
Array.Copy(fileBytes, newFileBytes, fileBytes.Length);
|
||||
fileBytes = newFileBytes;
|
||||
Logger.Info?.Print(LogClass.ServiceNfp, "Added 8 bytes to the end of the file.");
|
||||
}
|
||||
|
||||
AmiiboDecryptor amiiboDecryptor = new(keyRetailBinPath);
|
||||
|
@ -59,6 +71,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
|||
byte[] dataFull = amiiboDump.GetData();
|
||||
Logger.Debug?.Print(LogClass.ServiceNfp, $"Data Full Length: {dataFull.Length}");
|
||||
byte[] uid = new byte[7];
|
||||
byte[] mii = new byte[98];
|
||||
Array.Copy(dataFull, 0, uid, 0, 7);
|
||||
|
||||
byte bcc0 = CalculateBCC0(uid);
|
||||
|
@ -96,6 +109,10 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
|||
setID[0] = pageData[2];
|
||||
formData = pageData[3];
|
||||
break;
|
||||
case >= 40 and <= 63:
|
||||
int miiOffset = (page - 40) * 4;
|
||||
Array.Copy(pageData, 0, mii, miiOffset, 4);
|
||||
break;
|
||||
case 64:
|
||||
case 65:
|
||||
// Extract title ID
|
||||
|
@ -113,7 +130,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string usedCharacterStr = BitConverter.ToString(usedCharacter).Replace("-", "");
|
||||
string variationStr = BitConverter.ToString(variation).Replace("-", "");
|
||||
string amiiboIDStr = BitConverter.ToString(amiiboID).Replace("-", "");
|
||||
|
@ -141,14 +157,20 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
|||
LastWriteDate = writeDateTime,
|
||||
WriteCounter = writeCounterValue,
|
||||
};
|
||||
if (writeCounterValue > 0)
|
||||
VirtualAmiiboBinFile virtualAmiiboBinFile = new VirtualAmiiboBinFile
|
||||
{
|
||||
VirtualAmiibo.ApplicationBytes = applicationAreas;
|
||||
}
|
||||
VirtualAmiibo.NickName = nickName;
|
||||
ApplicationBytes = applicationAreas,
|
||||
MiiBytes = mii,
|
||||
InputBin = filePath,
|
||||
NickName = nickName,
|
||||
WriteCounter = writeCounterValue,
|
||||
LastWriteDate = writeDateTime,
|
||||
TagUuid = uid,
|
||||
};
|
||||
VirtualAmiibo.VirtualAmiiboBinFile = virtualAmiiboBinFile;
|
||||
return virtualAmiiboFile;
|
||||
}
|
||||
public static bool SaveBinFile(string inputFile, byte[] appData)
|
||||
public static bool SaveBinFile(string inputFile, VirtualAmiiboBinFile virtualAmiiboBinFile)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceNfp, "Saving bin file.");
|
||||
byte[] readBytes;
|
||||
|
@ -168,92 +190,32 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
|||
return false;
|
||||
}
|
||||
|
||||
if (appData.Length != 216) // Ensure application area size is valid
|
||||
if (readBytes.Length == 532)
|
||||
{
|
||||
// add 8 bytes to the end of the file
|
||||
byte[] newFileBytes = new byte[540];
|
||||
Array.Copy(readBytes, newFileBytes, readBytes.Length);
|
||||
readBytes = newFileBytes;
|
||||
}
|
||||
|
||||
AmiiboDecryptor amiiboDecryptor = new AmiiboDecryptor(keyRetailBinPath);
|
||||
AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(readBytes);
|
||||
amiiboDump.AmiiboNickname = virtualAmiiboBinFile.NickName;
|
||||
byte[] appData = virtualAmiiboBinFile.ApplicationBytes;
|
||||
if (appData.Length != 216)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceNfp, "Invalid application data length. Expected 216 bytes.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (readBytes.Length == 532)
|
||||
{
|
||||
// add 8 bytes to the end of the file
|
||||
byte[] newFileBytes = new byte[540];
|
||||
Array.Copy(readBytes, newFileBytes, readBytes.Length);
|
||||
readBytes = newFileBytes;
|
||||
}
|
||||
|
||||
AmiiboDecryptor amiiboDecryptor = new AmiiboDecryptor(keyRetailBinPath);
|
||||
AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(readBytes);
|
||||
|
||||
byte[] oldData = amiiboDump.GetData();
|
||||
if (oldData.Length != 540) // Verify the expected length for NTAG215 tags
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceNfp, "Invalid tag data length. Expected 540 bytes.");
|
||||
return false;
|
||||
}
|
||||
Array.Copy(appData, 0, oldData, 304, 216);
|
||||
// apply write counter and write date change
|
||||
byte[] writeCounter = BitConverter.GetBytes(virtualAmiiboBinFile.WriteCounter);
|
||||
byte[] writeDate = BitConverter.GetBytes((ushort)virtualAmiiboBinFile.LastWriteDate.Day);
|
||||
writeDate = BitConverter.GetBytes((ushort)(writeDate[0] | (virtualAmiiboBinFile.LastWriteDate.Month << 5) | (virtualAmiiboBinFile.LastWriteDate.Year - 2000) << 9));
|
||||
Array.Copy(writeCounter, 0, oldData, 264, 2);
|
||||
Array.Copy(writeDate, 0, oldData, 26, 2);
|
||||
|
||||
byte[] newData = new byte[oldData.Length];
|
||||
Array.Copy(oldData, newData, oldData.Length);
|
||||
|
||||
// Replace application area with appData
|
||||
int appAreaOffset = 76 * 4; // Starting page (76) times 4 bytes per page
|
||||
Array.Copy(appData, 0, newData, appAreaOffset, appData.Length);
|
||||
|
||||
AmiiboDump encryptedDump = amiiboDecryptor.EncryptAmiiboDump(newData);
|
||||
byte[] encryptedData = encryptedDump.GetData();
|
||||
|
||||
if (encryptedData == null || encryptedData.Length != readBytes.Length)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceNfp, "Failed to encrypt data correctly.");
|
||||
return false;
|
||||
}
|
||||
inputFile = inputFile.Replace("_modified", string.Empty);
|
||||
// Save the encrypted data to file or return it for saving externally
|
||||
string outputFilePath = Path.Combine(Path.GetDirectoryName(inputFile), Path.GetFileNameWithoutExtension(inputFile) + "_modified.bin");
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(outputFilePath, encryptedData);
|
||||
Logger.Info?.Print(LogClass.ServiceNfp, $"Modified Amiibo data saved to {outputFilePath}.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceNfp, $"Error saving file: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static bool SaveBinFile(string inputFile, string newNickName)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceNfp, "Saving bin file.");
|
||||
byte[] readBytes;
|
||||
try
|
||||
{
|
||||
readBytes = File.ReadAllBytes(inputFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceNfp, $"Error reading file: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
string keyRetailBinPath = GetKeyRetailBinPath();
|
||||
if (string.IsNullOrEmpty(keyRetailBinPath))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceNfp, "Key retail path is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (readBytes.Length == 532)
|
||||
{
|
||||
// add 8 bytes to the end of the file
|
||||
byte[] newFileBytes = new byte[540];
|
||||
Array.Copy(readBytes, newFileBytes, readBytes.Length);
|
||||
readBytes = newFileBytes;
|
||||
}
|
||||
|
||||
AmiiboDecryptor amiiboDecryptor = new AmiiboDecryptor(keyRetailBinPath);
|
||||
AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(readBytes);
|
||||
amiiboDump.AmiiboNickname = newNickName;
|
||||
byte[] oldData = amiiboDump.GetData();
|
||||
if (oldData.Length != 540) // Verify the expected length for NTAG215 tags
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceNfp, "Invalid tag data length. Expected 540 bytes.");
|
||||
|
|
257
src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs
Normal file
257
src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs
Normal file
|
@ -0,0 +1,257 @@
|
|||
using Ryujinx.HLE.HOS.Services.Mii.Types;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
||||
{
|
||||
internal class CharInfoBin
|
||||
{
|
||||
public byte MiiVersion { get; private set; }
|
||||
public byte[] CreateID { get; private set; }
|
||||
public bool AllowCopying { get; private set; }
|
||||
public bool ProfanityFlag { get; private set; }
|
||||
public int RegionLock { get; private set; }
|
||||
public int CharacterSet { get; private set; }
|
||||
public int PageIndex { get; private set; }
|
||||
public int SlotIndex { get; private set; }
|
||||
public int DeviceOrigin { get; private set; }
|
||||
public ulong SystemId { get; private set; }
|
||||
public uint MiiId { get; private set; }
|
||||
public string CreatorMac { get; private set; }
|
||||
public bool IsMale { get; private set; }
|
||||
public int BirthdayMonth { get; private set; }
|
||||
public int BirthdayDay { get; private set; }
|
||||
public int FavoriteColor { get; private set; }
|
||||
public bool FavoriteMii { get; private set; }
|
||||
public string MiiName { get; private set; }
|
||||
public string AuthorName { get; private set; }
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
public bool DisableSharing { get; private set; }
|
||||
public int FaceShape { get; private set; }
|
||||
public int SkinColor { get; private set; }
|
||||
public int Wrinkles { get; private set; }
|
||||
public int Makeup { get; private set; }
|
||||
public int HairStyle { get; private set; }
|
||||
public int HairColor { get; private set; }
|
||||
public bool FlipHair { get; private set; }
|
||||
public int EyeStyle { get; private set; }
|
||||
public int EyeColor { get; private set; }
|
||||
public int EyeScale { get; private set; }
|
||||
public int EyeYScale { get; private set; }
|
||||
public int EyeRotation { get; private set; }
|
||||
public int EyeXSpacing { get; private set; }
|
||||
public int EyeYPosition { get; private set; }
|
||||
public int EyebrowStyle { get; private set; }
|
||||
public int EyebrowColor { get; private set; }
|
||||
public int EyebrowScale { get; private set; }
|
||||
public int EyebrowYScale { get; private set; }
|
||||
public int EyebrowRotation { get; private set; }
|
||||
public int EyebrowXSpacing { get; private set; }
|
||||
public int EyebrowYPosition { get; private set; }
|
||||
public int NoseStyle { get; private set; }
|
||||
public int NoseScale { get; private set; }
|
||||
public int NoseYPosition { get; private set; }
|
||||
public int MouthStyle { get; private set; }
|
||||
public int MouthColor { get; private set; }
|
||||
public int MouthScale { get; private set; }
|
||||
public int MouthYScale { get; private set; }
|
||||
public int MouthYPosition { get; private set; }
|
||||
public int MustacheStyle { get; private set; }
|
||||
public int BeardStyle { get; private set; }
|
||||
public int BeardColor { get; private set; }
|
||||
public int MustacheScale { get; private set; }
|
||||
public int MustacheYPosition { get; private set; }
|
||||
public int GlassesStyle { get; private set; }
|
||||
public int GlassesColor { get; private set; }
|
||||
public int GlassesScale { get; private set; }
|
||||
public int GlassesYPosition { get; private set; }
|
||||
public bool MoleEnabled { get; private set; }
|
||||
public int MoleScale { get; private set; }
|
||||
public int MoleXPosition { get; private set; }
|
||||
public int MoleYPosition { get; private set; }
|
||||
public static CharInfoBin Parse(byte[] data)
|
||||
{
|
||||
if (data.Length < 0x5C)
|
||||
throw new ArgumentException("Data too short to parse Mii data.");
|
||||
|
||||
var mii = new CharInfoBin();
|
||||
|
||||
mii.MiiVersion = data[0x0];
|
||||
|
||||
byte flags1 = data[0x1];
|
||||
mii.AllowCopying = (flags1 & 0x1) != 0;
|
||||
mii.ProfanityFlag = (flags1 & 0x2) != 0;
|
||||
mii.RegionLock = (flags1 >> 2) & 0x3;
|
||||
mii.CharacterSet = (flags1 >> 4) & 0x3;
|
||||
// add the first 0x10 bytes to the create id
|
||||
mii.CreateID = data[0x0..0x10];
|
||||
byte position = data[0x2];
|
||||
mii.PageIndex = position & 0xF;
|
||||
mii.SlotIndex = (position >> 4) & 0xF;
|
||||
|
||||
byte deviceInfo = data[0x3];
|
||||
mii.DeviceOrigin = (deviceInfo >> 4) & 0x7;
|
||||
|
||||
mii.SystemId = BitConverter.ToUInt64(data, 0x4);
|
||||
|
||||
mii.MiiId = BitConverter.ToUInt32(data, 0xC);
|
||||
|
||||
mii.CreatorMac = BitConverter.ToString(data, 0x10, 0x6).Replace("-", ":");
|
||||
|
||||
ushort flags2 = BitConverter.ToUInt16(data, 0x18);
|
||||
mii.IsMale = (flags2 & 0x1) == 0;
|
||||
mii.BirthdayMonth = (flags2 >> 1) & 0xF;
|
||||
mii.BirthdayDay = (flags2 >> 5) & 0x1F;
|
||||
mii.FavoriteColor = (flags2 >> 10) & 0xF;
|
||||
mii.FavoriteMii = (flags2 & 0x4000) != 0;
|
||||
|
||||
mii.MiiName = ParseUtf16String(data, 0x1A, 20);
|
||||
|
||||
ushort dimensions = BitConverter.ToUInt16(data, 0x2E);
|
||||
mii.Width = dimensions & 0xFF;
|
||||
mii.Height = (dimensions >> 8) & 0xFF;
|
||||
|
||||
byte flags3 = data[0x30];
|
||||
mii.DisableSharing = (flags3 & 0x1) != 0;
|
||||
mii.FaceShape = (flags3 >> 1) & 0xF;
|
||||
mii.SkinColor = (flags3 >> 5) & 0x7;
|
||||
|
||||
byte flags4 = data[0x31];
|
||||
mii.Wrinkles = flags4 & 0xF;
|
||||
mii.Makeup = (flags4 >> 4) & 0xF;
|
||||
|
||||
mii.HairStyle = data[0x32];
|
||||
|
||||
byte hairInfo = data[0x33];
|
||||
mii.HairColor = hairInfo & 0x7;
|
||||
mii.FlipHair = (hairInfo & 0x8) != 0;
|
||||
|
||||
uint eyeInfo = BitConverter.ToUInt32(data, 0x34);
|
||||
mii.EyeStyle = (int)(eyeInfo & 0x3F);
|
||||
mii.EyeColor = (int)((eyeInfo >> 6) & 0x7);
|
||||
mii.EyeScale = (int)((eyeInfo >> 9) & 0xF);
|
||||
mii.EyeYScale = (int)((eyeInfo >> 13) & 0x7);
|
||||
mii.EyeRotation = (int)((eyeInfo >> 16) & 0x1F);
|
||||
mii.EyeXSpacing = (int)((eyeInfo >> 21) & 0xF);
|
||||
mii.EyeYPosition = (int)((eyeInfo >> 25) & 0x1F);
|
||||
|
||||
uint eyebrowInfo = BitConverter.ToUInt32(data, 0x38);
|
||||
mii.EyebrowStyle = (int)(eyebrowInfo & 0x1F);
|
||||
mii.EyebrowColor = (int)((eyebrowInfo >> 5) & 0x7);
|
||||
mii.EyebrowScale = (int)((eyebrowInfo >> 8) & 0xF);
|
||||
mii.EyebrowYScale = (int)((eyebrowInfo >> 12) & 0x7);
|
||||
mii.EyebrowRotation = (int)((eyebrowInfo >> 16) & 0xF);
|
||||
mii.EyebrowXSpacing = (int)((eyebrowInfo >> 21) & 0xF);
|
||||
mii.EyebrowYPosition = (int)((eyebrowInfo >> 25) & 0x1F);
|
||||
|
||||
ushort noseInfo = BitConverter.ToUInt16(data, 0x3C);
|
||||
mii.NoseStyle = noseInfo & 0x1F;
|
||||
mii.NoseScale = (noseInfo >> 5) & 0xF;
|
||||
mii.NoseYPosition = (noseInfo >> 9) & 0x1F;
|
||||
|
||||
ushort mouthInfo = BitConverter.ToUInt16(data, 0x3E);
|
||||
mii.MouthStyle = mouthInfo & 0x3F;
|
||||
mii.MouthColor = (mouthInfo >> 6) & 0x7;
|
||||
mii.MouthScale = (mouthInfo >> 9) & 0xF;
|
||||
mii.MouthYScale = (mouthInfo >> 13) & 0x7;
|
||||
|
||||
ushort miscInfo = BitConverter.ToUInt16(data, 0x40);
|
||||
mii.MouthYPosition = miscInfo & 0x1F;
|
||||
mii.MustacheStyle = (miscInfo >> 5) & 0x7;
|
||||
|
||||
ushort beardInfo = BitConverter.ToUInt16(data, 0x42);
|
||||
mii.BeardStyle = beardInfo & 0x7;
|
||||
mii.BeardColor = (beardInfo >> 3) & 0x7;
|
||||
|
||||
ushort mustacheInfo = BitConverter.ToUInt16(data, 0x44);
|
||||
mii.MustacheScale = mustacheInfo & 0xF;
|
||||
mii.MustacheYPosition = (mustacheInfo >> 4) & 0x1F;
|
||||
|
||||
ushort glassesInfo = BitConverter.ToUInt16(data, 0x46);
|
||||
mii.GlassesStyle = glassesInfo & 0xF;
|
||||
mii.GlassesColor = (glassesInfo >> 4) & 0x7;
|
||||
mii.GlassesScale = (glassesInfo >> 7) & 0xF;
|
||||
mii.GlassesYPosition = (glassesInfo >> 11) & 0x1F;
|
||||
|
||||
byte moleFlags = data[0x48];
|
||||
mii.MoleEnabled = (moleFlags & 0x1) != 0;
|
||||
mii.MoleScale = (moleFlags >> 1) & 0xF;
|
||||
|
||||
ushort moleInfo = BitConverter.ToUInt16(data, 0x46);
|
||||
mii.MoleEnabled = (moleInfo & 0x1) != 0;
|
||||
mii.MoleScale = (moleInfo >> 1) & 0xF; // 4 bits for scale
|
||||
mii.MoleXPosition = (moleInfo >> 5) & 0x1F; // 5 bits for X position
|
||||
mii.MoleYPosition = (moleInfo >> 10) & 0x1F; // 5 bits for Y position
|
||||
|
||||
|
||||
return mii;
|
||||
}
|
||||
|
||||
private static string ParseUtf16String(byte[] data, int offset, int maxLength)
|
||||
{
|
||||
int length = 0;
|
||||
for (int i = 0; i < maxLength * 2; i += 2)
|
||||
{
|
||||
if (data[offset + i] == 0 && data[offset + i + 1] == 0)
|
||||
break;
|
||||
length++;
|
||||
}
|
||||
|
||||
return Encoding.Unicode.GetString(data, offset, length * 2);
|
||||
}
|
||||
|
||||
public CharInfo ConvertToCharInfo(CharInfo Info)
|
||||
{
|
||||
//UInt128 CreateId = BitConverter.ToUInt128(CreateID, 0);
|
||||
//Info.CreateId = new CreateId(CreateId);
|
||||
Info.Nickname = Nickname.FromString(MiiName);
|
||||
Info.FavoriteColor = (byte)FavoriteColor;
|
||||
Info.Gender = IsMale ? Gender.Male : Gender.Female;
|
||||
Info.Height = (byte)Height;
|
||||
Info.FacelineType = (FacelineType)FaceShape;
|
||||
Info.FacelineColor = (FacelineColor)SkinColor;
|
||||
Info.FacelineWrinkle = (FacelineWrinkle)Wrinkles;
|
||||
Info.FacelineMake = (FacelineMake)Makeup;
|
||||
Info.HairType = (HairType)HairStyle;
|
||||
Info.HairColor = (CommonColor)HairColor;
|
||||
Info.HairFlip = FlipHair ? HairFlip.Left : HairFlip.Right;
|
||||
Info.EyeType = (EyeType)EyeStyle;
|
||||
Info.EyeColor = (CommonColor)EyeColor;
|
||||
Info.EyeScale = (byte)EyeScale;
|
||||
Info.EyeAspect = (byte)EyeYScale;
|
||||
Info.EyeRotate = (byte)EyeRotation;
|
||||
Info.EyeX = (byte)EyeXSpacing;
|
||||
Info.EyeY = (byte)EyeYPosition;
|
||||
Info.EyebrowType = (EyebrowType)EyebrowStyle;
|
||||
Info.EyebrowColor = (CommonColor)EyebrowColor;
|
||||
Info.EyebrowScale = (byte)EyebrowScale;
|
||||
Info.EyebrowAspect = (byte)EyebrowYScale;
|
||||
Info.EyebrowRotate = (byte)EyebrowRotation;
|
||||
Info.EyebrowX = (byte)EyebrowXSpacing;
|
||||
Info.EyebrowY = (byte)EyebrowYPosition;
|
||||
Info.NoseType = (NoseType)NoseStyle;
|
||||
Info.NoseScale = (byte)NoseScale;
|
||||
Info.NoseY = (byte)NoseYPosition;
|
||||
Info.MouthType = (MouthType)MouthStyle;
|
||||
Info.MouthColor = (CommonColor)MouthColor;
|
||||
Info.MouthScale = (byte)MouthScale;
|
||||
Info.MouthAspect = (byte)MouthYScale;
|
||||
Info.MouthY = (byte)MouthYPosition;
|
||||
Info.BeardType = (BeardType)BeardStyle;
|
||||
Info.BeardColor = (CommonColor)BeardColor;
|
||||
Info.MustacheType = (MustacheType)MustacheStyle;
|
||||
Info.MustacheScale = (byte)MustacheScale;
|
||||
Info.MustacheY = (byte)MustacheYPosition;
|
||||
Info.GlassType = (GlassType)GlassesStyle;
|
||||
Info.GlassColor = (CommonColor)GlassesColor;
|
||||
Info.GlassScale = (byte)GlassesScale;
|
||||
Info.GlassY = (byte)GlassesYPosition;
|
||||
Info.MoleType = MoleEnabled ? MoleType.OneDot : MoleType.None;
|
||||
Info.MoleScale = (byte)MoleScale;
|
||||
Info.MoleX = (byte)MoleXPosition;
|
||||
Info.MoleY = (byte)MoleYPosition;
|
||||
return Info;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Services.Mii;
|
||||
using Ryujinx.HLE.HOS.Services.Mii.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption
|
||||
{
|
||||
public class VirtualAmiiboBinFile
|
||||
{
|
||||
public byte[] ApplicationBytes { get; set; }
|
||||
public byte[] MiiBytes { get; set; }
|
||||
public string InputBin { get; set; }
|
||||
public string NickName { get; set; }
|
||||
public int WriteCounter { get; set; }
|
||||
public DateTime LastWriteDate { get; set; }
|
||||
public byte[] TagUuid { get; set; }
|
||||
|
||||
internal RegisterInfo GetRegisterInfo(ITickSource tickSource)
|
||||
{
|
||||
RegisterInfo info = new RegisterInfo();
|
||||
info.FirstWriteYear = (ushort)LastWriteDate.Year;
|
||||
info.FirstWriteMonth = (byte)LastWriteDate.Month;
|
||||
info.FirstWriteDay = (byte)LastWriteDate.Day;
|
||||
byte[] nicknameBytes = Encoding.UTF8.GetBytes(NickName);
|
||||
nicknameBytes.CopyTo(info.Nickname.AsSpan());
|
||||
byte[] newMiiBytes = new byte[92];
|
||||
Array.Copy(MiiBytes, 0, newMiiBytes, 0, 92);
|
||||
CharInfoBin charInfoBin = CharInfoBin.Parse(newMiiBytes);
|
||||
UtilityImpl utilityImpl = new UtilityImpl(tickSource);
|
||||
CharInfo Info = new();
|
||||
Info.SetFromStoreData(StoreData.BuildDefault(utilityImpl, 0));
|
||||
CharInfo charInfo = charInfoBin.ConvertToCharInfo(Info);
|
||||
info.MiiCharInfo = charInfo;
|
||||
return info;
|
||||
}
|
||||
public void UpdateApplicationArea(byte[] applicationAreaData)
|
||||
{
|
||||
ApplicationBytes = applicationAreaData;
|
||||
WriteCounter++;
|
||||
LastWriteDate = DateTime.Now;
|
||||
SaveFile();
|
||||
}
|
||||
|
||||
public void UpdateNickName(string newNickName)
|
||||
{
|
||||
NickName = newNickName;
|
||||
SaveFile();
|
||||
}
|
||||
|
||||
public bool SaveFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
AmiiboBinReader.SaveBinFile(InputBin, this);
|
||||
VirtualAmiibo.VirtualAmiiboBinFile = null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,9 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
static class VirtualAmiibo
|
||||
{
|
||||
public static uint OpenedApplicationAreaId;
|
||||
public static byte[] ApplicationBytes = Array.Empty<byte>();
|
||||
public static string InputBin = string.Empty;
|
||||
public static string NickName = string.Empty;
|
||||
public static VirtualAmiiboBinFile VirtualAmiiboBinFile = null;
|
||||
private static readonly AmiiboJsonSerializerContext _serializerContext = AmiiboJsonSerializerContext.Default;
|
||||
public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid)
|
||||
{
|
||||
|
@ -69,13 +67,12 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
|
||||
public static RegisterInfo GetRegisterInfo(ITickSource tickSource, string amiiboId, string userName)
|
||||
{
|
||||
if (VirtualAmiiboBinFile!=null)
|
||||
{
|
||||
return VirtualAmiiboBinFile.GetRegisterInfo(tickSource);
|
||||
}
|
||||
VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId);
|
||||
string nickname = amiiboFile.NickName ?? "Ryujinx";
|
||||
if (NickName != string.Empty)
|
||||
{
|
||||
nickname = NickName;
|
||||
NickName = string.Empty;
|
||||
}
|
||||
UtilityImpl utilityImpl = new(tickSource);
|
||||
CharInfo charInfo = new();
|
||||
|
||||
|
@ -103,20 +100,20 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
|
||||
public static void UpdateNickName(string amiiboId, string newNickName)
|
||||
{
|
||||
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
|
||||
virtualAmiiboFile.NickName = newNickName;
|
||||
if (InputBin != string.Empty)
|
||||
if (VirtualAmiiboBinFile != null)
|
||||
{
|
||||
AmiiboBinReader.SaveBinFile(InputBin, virtualAmiiboFile.NickName);
|
||||
VirtualAmiiboBinFile.UpdateNickName(newNickName);
|
||||
return;
|
||||
}
|
||||
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
|
||||
virtualAmiiboFile.NickName = newNickName;
|
||||
SaveAmiiboFile(virtualAmiiboFile);
|
||||
}
|
||||
|
||||
public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId)
|
||||
{
|
||||
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
|
||||
if (ApplicationBytes.Length > 0)
|
||||
if (VirtualAmiiboBinFile != null)
|
||||
{
|
||||
OpenedApplicationAreaId = applicationAreaId;
|
||||
return true;
|
||||
|
@ -134,11 +131,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
|
||||
public static byte[] GetApplicationArea(string amiiboId)
|
||||
{
|
||||
if (ApplicationBytes.Length > 0)
|
||||
if (VirtualAmiiboBinFile != null)
|
||||
{
|
||||
byte[] bytes = ApplicationBytes;
|
||||
ApplicationBytes = Array.Empty<byte>();
|
||||
return bytes;
|
||||
return VirtualAmiiboBinFile.ApplicationBytes;
|
||||
}
|
||||
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
|
||||
|
||||
|
@ -175,9 +170,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
|
||||
public static void SetApplicationArea(string amiiboId, byte[] applicationAreaData)
|
||||
{
|
||||
if (InputBin != string.Empty)
|
||||
if (VirtualAmiiboBinFile != null)
|
||||
{
|
||||
AmiiboBinReader.SaveBinFile(InputBin, applicationAreaData);
|
||||
VirtualAmiiboBinFile.UpdateApplicationArea(applicationAreaData);
|
||||
return;
|
||||
}
|
||||
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
|
||||
|
@ -241,11 +236,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
|
||||
public static bool SaveFileExists(VirtualAmiiboFile virtualAmiiboFile)
|
||||
{
|
||||
if (InputBin != string.Empty)
|
||||
if (VirtualAmiiboBinFile != null)
|
||||
{
|
||||
SaveAmiiboFile(virtualAmiiboFile);
|
||||
return true;
|
||||
|
||||
return VirtualAmiiboBinFile.SaveFile();
|
||||
}
|
||||
return File.Exists(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json"));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue