Implement mii:u and mii:e entirely (#955)

* Implement mii:u and mii:e entirely

Co-authored-by: AcK77 <Acoustik666@gmail.com>

This commit implement the mii service accurately.

This is based on Ac_k work but was polished and updated to 7.x.

Please note that the following calls are partially implemented:

- Convert: Used to convert from old console format (Wii/Wii U/3ds)
- Import and Export: this is shouldn't be accesible in production mode.

* Remove some debug leftovers

* Make it possible to load an arbitrary mii database from a Switch

* Address gdk's comments

* Reduce visibility of all the Mii code

* Address Ac_K's comments

* Remove the StructLayout of DatabaseSessionMetadata

* Add a missing line return in DatabaseSessionMetadata

* Misc fixes and style changes

* Fix some issues from last commit

* Fix database server metadata UpdateCounter in MarkDirty (Thanks Moose for the catch)

* MountCounter should only be incremented when no error is reported

* Fix FixDatabase

Co-authored-by: Alex Barney <thealexbarney@gmail.com>
This commit is contained in:
Thog 2020-03-01 23:56:02 +01:00 committed by GitHub
parent 7d1a294eae
commit 3b531de670
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 6728 additions and 15 deletions

View file

@ -14,6 +14,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
using Ryujinx.HLE.HOS.Services.Settings;
using Ryujinx.HLE.HOS.Services.Sm;
@ -232,6 +233,8 @@ namespace Ryujinx.HLE.HOS
// FIXME: TimeZone shoud be init here but it's actually done in ContentManager
TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
DatabaseImpl.Instance.InitializeDatabase(device);
}
public void LoadCart(string exeFsDir, string romFsFile = null)

View file

@ -1,4 +1,5 @@
using LibHac.Account;
using Ryujinx.HLE.Utilities;
using System;
using System.IO;
using System.Linq;
@ -73,9 +74,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
return HashCode.Combine(Low, High);
}
public Uid ToLibHacUid()
public readonly Uid ToLibHacUid()
{
return new Uid((ulong)High, (ulong)Low);
}
public readonly UInt128 ToUInt128()
{
return new UInt128(Low, High);
}
}
}

View file

@ -0,0 +1,326 @@
using Ryujinx.HLE.HOS.Services.Mii.Types;
using System;
namespace Ryujinx.HLE.HOS.Services.Mii
{
class DatabaseImpl
{
private static DatabaseImpl _instance;
public static DatabaseImpl Instance
{
get
{
if (_instance == null)
{
_instance = new DatabaseImpl();
}
return _instance;
}
}
private UtilityImpl _utilityImpl;
private MiiDatabaseManager _miiDatabase;
private bool _isBroken;
public DatabaseImpl()
{
_utilityImpl = new UtilityImpl();
_miiDatabase = new MiiDatabaseManager();
}
public bool IsUpdated(DatabaseSessionMetadata metadata, SourceFlag flag)
{
if (flag.HasFlag(SourceFlag.Database))
{
return _miiDatabase.IsUpdated(metadata);
}
return false;
}
public bool IsBrokenDatabaseWithClearFlag()
{
bool result = _isBroken;
if (_isBroken)
{
_isBroken = false;
Format(new DatabaseSessionMetadata(0, new SpecialMiiKeyCode()));
}
return result;
}
public bool IsFullDatabase()
{
return _miiDatabase.IsFullDatabase();
}
private ResultCode GetDefault<T>(SourceFlag flag, ref int count, Span<T> elements) where T : struct, IElement
{
if (!flag.HasFlag(SourceFlag.Default))
{
return ResultCode.Success;
}
for (uint i = 0; i < DefaultMii.TableLength; i++)
{
if (count >= elements.Length)
{
return ResultCode.BufferTooSmall;
}
elements[count] = default;
elements[count].SetFromStoreData(StoreData.BuildDefault(_utilityImpl, i));
elements[count].SetSource(Source.Default);
count++;
}
return ResultCode.Success;
}
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, IStoredData<T> oldMiiData, SourceFlag flag, IStoredData<T> newMiiData) where T : unmanaged
{
if (!flag.HasFlag(SourceFlag.Database))
{
return ResultCode.NotFound;
}
if (metadata.IsInterfaceVersionSupported(1) && !oldMiiData.IsValid())
{
return oldMiiData.InvalidData;
}
ResultCode result = _miiDatabase.FindIndex(metadata, out int index, oldMiiData.CreateId);
if (result == ResultCode.Success)
{
_miiDatabase.Get(metadata, index, out StoreData storeData);
if (storeData.Type != oldMiiData.Type)
{
return ResultCode.NotFound;
}
newMiiData.SetFromStoreData(storeData);
if (oldMiiData == newMiiData)
{
return ResultCode.NotUpdated;
}
}
return result;
}
public ResultCode Get<T>(DatabaseSessionMetadata metadata, SourceFlag flag, out int count, Span<T> elements) where T : struct, IElement
{
count = 0;
if (!flag.HasFlag(SourceFlag.Database))
{
return GetDefault(flag, ref count, elements);
}
int databaseCount = _miiDatabase.GetCount(metadata);
for (int i = 0; i < databaseCount; i++)
{
if (count >= elements.Length)
{
return ResultCode.BufferTooSmall;
}
_miiDatabase.Get(metadata, i, out StoreData storeData);
elements[count] = default;
elements[count].SetFromStoreData(storeData);
elements[count].SetSource(Source.Database);
count++;
}
return GetDefault(flag, ref count, elements);
}
public ResultCode InitializeDatabase(Switch device)
{
_miiDatabase.InitializeDatabase(device);
_miiDatabase.LoadFromFile(out _isBroken);
// Nintendo ignore any error code from before
return ResultCode.Success;
}
public DatabaseSessionMetadata CreateSessionMetadata(SpecialMiiKeyCode miiKeyCode)
{
return _miiDatabase.CreateSessionMetadata(miiKeyCode);
}
public void SetInterfaceVersion(DatabaseSessionMetadata metadata, uint interfaceVersion)
{
_miiDatabase.SetInterfaceVersion(metadata, interfaceVersion);
}
public void Format(DatabaseSessionMetadata metadata)
{
_miiDatabase.FormatDatabase(metadata);
_miiDatabase.SaveDatabase();
}
public ResultCode DestroyFile(DatabaseSessionMetadata metadata)
{
_isBroken = true;
return _miiDatabase.DestroyFile(metadata);
}
public void BuildDefault(uint index, out CharInfo charInfo)
{
StoreData storeData = StoreData.BuildDefault(_utilityImpl, index);
charInfo = default;
charInfo.SetFromStoreData(storeData);
}
public void BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo)
{
StoreData storeData = StoreData.BuildRandom(_utilityImpl, age, gender, race);
charInfo = default;
charInfo.SetFromStoreData(storeData);
}
public ResultCode DeleteFile()
{
return _miiDatabase.DeleteFile();
}
public ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo)
{
charInfo = new CharInfo();
if (!coreData.IsValid())
{
return ResultCode.InvalidCoreData;
}
StoreData storeData = StoreData.BuildFromCoreData(_utilityImpl, coreData);
if (!storeData.CoreData.Nickname.IsValidForFontRegion(storeData.CoreData.FontRegion))
{
storeData.CoreData.Nickname = Nickname.Question;
storeData.UpdateCrc();
}
charInfo.SetFromStoreData(storeData);
return ResultCode.Success;
}
public int FindIndex(CreateId createId, bool isSpecial)
{
if (_miiDatabase.FindIndex(out int index, createId, isSpecial) == ResultCode.Success)
{
return index;
}
return -1;
}
public uint GetCount(DatabaseSessionMetadata metadata, SourceFlag flag)
{
int count = 0;
if (flag.HasFlag(SourceFlag.Default))
{
count += DefaultMii.TableLength;
}
if (flag.HasFlag(SourceFlag.Database))
{
count += _miiDatabase.GetCount(metadata);
}
return (uint)count;
}
public ResultCode Move(DatabaseSessionMetadata metadata, int index, CreateId createId)
{
ResultCode result = _miiDatabase.Move(metadata, index, createId);
if (result == ResultCode.Success)
{
result = _miiDatabase.SaveDatabase();
}
return result;
}
public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId)
{
ResultCode result = _miiDatabase.Delete(metadata, createId);
if (result == ResultCode.Success)
{
result = _miiDatabase.SaveDatabase();
}
return result;
}
public ResultCode AddOrReplace(DatabaseSessionMetadata metadata, StoreData storeData)
{
ResultCode result = _miiDatabase.AddOrReplace(metadata, storeData);
if (result == ResultCode.Success)
{
result = _miiDatabase.SaveDatabase();
}
return result;
}
public ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData)
{
coreData = new CoreData();
if (charInfo.IsValid())
{
return ResultCode.InvalidCharInfo;
}
coreData.SetFromCharInfo(charInfo);
if (!coreData.Nickname.IsValidForFontRegion(coreData.FontRegion))
{
coreData.Nickname = Nickname.Question;
}
return ResultCode.Success;
}
public ResultCode GetIndex(DatabaseSessionMetadata metadata, CharInfo charInfo, out int index)
{
if (!charInfo.IsValid())
{
index = -1;
return ResultCode.InvalidCharInfo;
}
if (_miiDatabase.FindIndex(out index, charInfo.CreateId, metadata.MiiKeyCode.IsEnabledSpecialMii()) != ResultCode.Success)
{
return ResultCode.NotFound;
}
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,24 @@
using Ryujinx.HLE.HOS.Services.Mii.Types;
namespace Ryujinx.HLE.HOS.Services.Mii
{
class DatabaseSessionMetadata
{
public uint InterfaceVersion;
public ulong UpdateCounter;
public SpecialMiiKeyCode MiiKeyCode { get; private set; }
public DatabaseSessionMetadata(ulong updateCounter, SpecialMiiKeyCode miiKeyCode)
{
InterfaceVersion = 0;
UpdateCounter = updateCounter;
MiiKeyCode = miiKeyCode;
}
public bool IsInterfaceVersionSupported(uint interfaceVersion)
{
return InterfaceVersion >= interfaceVersion;
}
}
}

View file

@ -0,0 +1,44 @@
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Utilities;
using System;
using System.Buffers.Binary;
namespace Ryujinx.HLE.HOS.Services.Mii
{
static class Helper
{
public static ushort CalculateCrc16BE(ReadOnlySpan<byte> data, int crc = 0)
{
const ushort poly = 0x1021;
for (int i = 0; i < data.Length; i++)
{
crc ^= data[i] << 8;
for (int j = 0; j < 8; j++)
{
crc <<= 1;
if ((crc & 0x10000) != 0)
{
crc = (crc ^ poly) & 0xFFFF;
}
}
}
return BinaryPrimitives.ReverseEndianness((ushort)crc);
}
public static UInt128 GetDeviceId()
{
// FIXME: call set:sys GetMiiAuthorId
return SystemStateMgr.DefaultUserId.ToUInt128();
}
public static ReadOnlySpan<byte> Ver3FacelineColorTable => new byte[] { 0, 1, 2, 3, 4, 5 };
public static ReadOnlySpan<byte> Ver3HairColorTable => new byte[] { 8, 1, 2, 3, 4, 5, 6, 7 };
public static ReadOnlySpan<byte> Ver3EyeColorTable => new byte[] { 8, 9, 10, 11, 12, 13 };
public static ReadOnlySpan<byte> Ver3MouthColorTable => new byte[] { 19, 20, 21, 22, 23 };
public static ReadOnlySpan<byte> Ver3GlassColorTable => new byte[] { 8, 14, 15, 16, 17, 18, 0 };
}
}

View file

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.HOS.Services.Sdb.Mii
namespace Ryujinx.HLE.HOS.Services.Mii
{
[Service("miiimg")] // 5.0.0+
class IImageDatabaseService : IpcService

View file

@ -0,0 +1,32 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Services.Mii.StaticService;
using Ryujinx.HLE.HOS.Services.Mii.Types;
namespace Ryujinx.HLE.HOS.Services.Mii
{
[Service("mii:e", true)]
[Service("mii:u", false)]
class IStaticService : IpcService
{
private DatabaseImpl _databaseImpl;
private bool _isSystem;
public IStaticService(ServiceCtx context, bool isSystem)
{
_isSystem = isSystem;
_databaseImpl = DatabaseImpl.Instance;
}
[Command(0)]
// GetDatabaseService(u32 mii_key_code) -> object<nn::mii::detail::IDatabaseService>
public ResultCode GetDatabaseService(ServiceCtx context)
{
SpecialMiiKeyCode miiKeyCode = context.RequestData.ReadStruct<SpecialMiiKeyCode>();
MakeObject(context, new DatabaseServiceImpl(_databaseImpl, _isSystem, miiKeyCode));
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,510 @@
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.Ncm;
using Ryujinx.HLE.HOS.Services.Mii.Types;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.HOS.Services.Mii
{
class MiiDatabaseManager
{
private static bool IsTestModeEnabled = false;
private static uint MountCounter = 0;
private const ulong DatabaseTestSaveDataId = 0x8000000000000031;
private const ulong DatabaseSaveDataId = 0x8000000000000030;
private const ulong NsTitleId = 0x010000000000001F;
private const ulong SdbTitleId = 0x0100000000000039;
private const string DatabasePath = "mii:/MiiDatabase.dat";
private const string MountName = "mii";
private NintendoFigurineDatabase _database;
private bool _isDirty;
private FileSystemClient _filesystemClient;
protected ulong UpdateCounter { get; private set; }
public MiiDatabaseManager()
{
_database = new NintendoFigurineDatabase();
_isDirty = false;
UpdateCounter = 0;
}
private void ResetDatabase()
{
_database = new NintendoFigurineDatabase();
_database.Format();
}
private void MarkDirty(DatabaseSessionMetadata metadata)
{
_isDirty = true;
UpdateCounter++;
metadata.UpdateCounter = UpdateCounter;
}
private bool GetAtVirtualIndex(int index, out int realIndex, out StoreData storeData)
{
realIndex = -1;
storeData = new StoreData();
int virtualIndex = 0;
for (int i = 0; i < _database.Length; i++)
{
StoreData tmp = _database.Get(i);
if (!tmp.IsSpecial())
{
if (index == virtualIndex)
{
realIndex = i;
storeData = tmp;
return true;
}
virtualIndex++;
}
}
return false;
}
private int ConvertRealIndexToVirtualIndex(int realIndex)
{
int virtualIndex = 0;
for (int i = 0; i < realIndex; i++)
{
StoreData tmp = _database.Get(i);
if (!tmp.IsSpecial())
{
virtualIndex++;
}
}
return virtualIndex;
}
public void InitializeDatabase(Switch device)
{
_filesystemClient = device.FileSystem.FsClient;
// Ensure we have valid data in the database
_database.Format();
MountSave();
}
private Result MountSave()
{
Result result = Result.Success;
if (MountCounter == 0)
{
ulong targetSaveDataId;
ulong targetTitleId;
if (IsTestModeEnabled)
{
targetSaveDataId = DatabaseTestSaveDataId;
targetTitleId = SdbTitleId;
}
else
{
targetSaveDataId = DatabaseSaveDataId;
// Nintendo use NS TitleID when creating the production save even on sdb, let's follow that behaviour.
targetTitleId = NsTitleId;
}
U8Span mountName = new U8Span(MountName);
result = _filesystemClient.MountSystemSaveData(mountName, SaveDataSpaceId.System, targetSaveDataId);
if (result.IsFailure())
{
if (ResultFs.TargetNotFound == result)
{
// TODO: We're currently always specifying the owner ID because FS doesn't have a way of
// knowing which process called it
result = _filesystemClient.CreateSystemSaveData(targetSaveDataId, new TitleId(targetTitleId), 0x10000, 0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData);
if (result.IsFailure()) return result;
result = _filesystemClient.MountSystemSaveData(mountName, SaveDataSpaceId.System, targetSaveDataId);
if (result.IsFailure()) return result;
}
}
if (result == Result.Success)
{
MountCounter++;
}
}
return result;
}
public ResultCode DeleteFile()
{
ResultCode result = (ResultCode)_filesystemClient.DeleteFile(DatabasePath).Value;
_filesystemClient.Commit(MountName);
return result;
}
public ResultCode LoadFromFile(out bool isBroken)
{
isBroken = false;
if (MountCounter == 0)
{
return ResultCode.InvalidArgument;
}
UpdateCounter++;
ResetDatabase();
Result result = _filesystemClient.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Read);
if (result.IsSuccess())
{
result = _filesystemClient.GetFileSize(out long fileSize, handle);
if (result.IsSuccess())
{
if (fileSize == Unsafe.SizeOf<NintendoFigurineDatabase>())
{
result = _filesystemClient.ReadFile(handle, 0, _database.AsSpan());
if (result.IsSuccess())
{
if (_database.Verify() != ResultCode.Success)
{
ResetDatabase();
isBroken = true;
}
else
{
isBroken = _database.FixDatabase();
}
}
}
else
{
isBroken = true;
}
}
_filesystemClient.CloseFile(handle);
return (ResultCode)result.Value;
}
else if (result == ResultFs.PathNotFound)
{
return (ResultCode)ForceSaveDatabase().Value;
}
return ResultCode.Success;
}
private Result ForceSaveDatabase()
{
Result result = _filesystemClient.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
if (result.IsSuccess() || result == ResultFs.PathAlreadyExists)
{
result = _filesystemClient.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Write);
if (result.IsSuccess())
{
result = _filesystemClient.GetFileSize(out long fileSize, handle);
if (result.IsSuccess())
{
// If the size doesn't match, recreate the file
if (fileSize != Unsafe.SizeOf<NintendoFigurineDatabase>())
{
_filesystemClient.CloseFile(handle);
result = _filesystemClient.DeleteFile(DatabasePath);
if (result.IsSuccess())
{
result = _filesystemClient.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
if (result.IsSuccess())
{
result = _filesystemClient.OpenFile(out handle, DatabasePath, OpenMode.Write);
}
}
if (result.IsFailure())
{
return result;
}
}
result = _filesystemClient.WriteFile(handle, 0, _database.AsReadOnlySpan(), WriteOption.Flush);
}
_filesystemClient.CloseFile(handle);
}
}
if (result.IsSuccess())
{
_isDirty = false;
result = _filesystemClient.Commit(MountName);
}
return result;
}
public DatabaseSessionMetadata CreateSessionMetadata(SpecialMiiKeyCode miiKeyCode)
{
return new DatabaseSessionMetadata(UpdateCounter, miiKeyCode);
}
public void SetInterfaceVersion(DatabaseSessionMetadata metadata, uint interfaceVersion)
{
metadata.InterfaceVersion = interfaceVersion;
}
public bool IsUpdated(DatabaseSessionMetadata metadata)
{
bool result = metadata.UpdateCounter != UpdateCounter;
metadata.UpdateCounter = UpdateCounter;
return result;
}
public int GetCount(DatabaseSessionMetadata metadata)
{
if (!metadata.MiiKeyCode.IsEnabledSpecialMii())
{
int count = 0;
for (int i = 0; i < _database.Length; i++)
{
StoreData tmp = _database.Get(i);
if (!tmp.IsSpecial())
{
count++;
}
}
return count;
}
else
{
return _database.Length;
}
}
public void Get(DatabaseSessionMetadata metadata, int index, out StoreData storeData)
{
if (!metadata.MiiKeyCode.IsEnabledSpecialMii())
{
if (GetAtVirtualIndex(index, out int realIndex, out _))
{
index = realIndex;
}
else
{
index = 0;
}
}
storeData = _database.Get(index);
}
public ResultCode FindIndex(DatabaseSessionMetadata metadata, out int index, CreateId createId)
{
return FindIndex(out index, createId, metadata.MiiKeyCode.IsEnabledSpecialMii());
}
public ResultCode FindIndex(out int index, CreateId createId, bool isSpecial)
{
if (_database.GetIndexByCreatorId(out int realIndex, createId))
{
if (isSpecial)
{
index = realIndex;
return ResultCode.Success;
}
StoreData storeData = _database.Get(realIndex);
if (!storeData.IsSpecial())
{
if (realIndex < 1)
{
index = 0;
}
else
{
index = ConvertRealIndexToVirtualIndex(realIndex);
}
return ResultCode.Success;
}
}
index = -1;
return ResultCode.NotFound;
}
public ResultCode Move(DatabaseSessionMetadata metadata, int newIndex, CreateId createId)
{
if (!metadata.MiiKeyCode.IsEnabledSpecialMii())
{
if (GetAtVirtualIndex(newIndex, out int realIndex, out _))
{
newIndex = realIndex;
}
else
{
newIndex = 0;
}
}
if (_database.GetIndexByCreatorId(out int oldIndex, createId))
{
StoreData realStoreData = _database.Get(oldIndex);
if (!metadata.MiiKeyCode.IsEnabledSpecialMii() && realStoreData.IsSpecial())
{
return ResultCode.InvalidOperationOnSpecialMii;
}
ResultCode result = _database.Move(newIndex, oldIndex);
if (result == ResultCode.Success)
{
MarkDirty(metadata);
}
return result;
}
return ResultCode.NotFound;
}
public ResultCode AddOrReplace(DatabaseSessionMetadata metadata, StoreData storeData)
{
if (!storeData.IsValid())
{
return ResultCode.InvalidStoreData;
}
if (!metadata.MiiKeyCode.IsEnabledSpecialMii() && !storeData.IsSpecial())
{
if (_database.GetIndexByCreatorId(out int index, storeData.CreateId))
{
StoreData oldStoreData = _database.Get(index);
if (oldStoreData.IsSpecial())
{
return ResultCode.InvalidOperationOnSpecialMii;
}
_database.Replace(index, storeData);
}
else
{
if (_database.IsFull())
{
return ResultCode.DatabaseFull;
}
_database.Add(storeData);
}
MarkDirty(metadata);
return ResultCode.Success;
}
return ResultCode.InvalidOperationOnSpecialMii;
}
public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId)
{
if (!_database.GetIndexByCreatorId(out int index, createId))
{
return ResultCode.NotFound;
}
if (!metadata.MiiKeyCode.IsEnabledSpecialMii())
{
StoreData storeData = _database.Get(index);
if (storeData.IsSpecial())
{
return ResultCode.InvalidOperationOnSpecialMii;
}
}
_database.Delete(index);
MarkDirty(metadata);
return ResultCode.Success;
}
public ResultCode DestroyFile(DatabaseSessionMetadata metadata)
{
_database.CorruptDatabase();
MarkDirty(metadata);
ResultCode result = SaveDatabase();
ResetDatabase();
return result;
}
public ResultCode SaveDatabase()
{
if (_isDirty)
{
return (ResultCode)ForceSaveDatabase().Value;
}
else
{
return ResultCode.NotUpdated;
}
}
public void FormatDatabase(DatabaseSessionMetadata metadata)
{
_database.Format();
MarkDirty(metadata);
}
public bool IsFullDatabase()
{
return _database.IsFull();
}
}
}

View file

@ -0,0 +1,29 @@
namespace Ryujinx.HLE.HOS.Services.Mii
{
public enum ResultCode
{
ModuleId = 126,
ErrorCodeShift = 9,
Success = 0,
InvalidArgument = (1 << ErrorCodeShift) | ModuleId,
BufferTooSmall = (2 << ErrorCodeShift) | ModuleId,
NotUpdated = (3 << ErrorCodeShift) | ModuleId,
NotFound = (4 << ErrorCodeShift) | ModuleId,
DatabaseFull = (5 << ErrorCodeShift) | ModuleId,
InvalidCharInfo = (100 << ErrorCodeShift) | ModuleId,
InvalidCrc = (101 << ErrorCodeShift) | ModuleId,
InvalidDeviceCrc = (102 << ErrorCodeShift) | ModuleId,
InvalidDatabaseMagic = (103 << ErrorCodeShift) | ModuleId,
InvalidDatabaseVersion = (104 << ErrorCodeShift) | ModuleId,
InvalidDatabaseSize = (105 << ErrorCodeShift) | ModuleId,
InvalidCreateId = (106 << ErrorCodeShift) | ModuleId,
InvalidCoreData = (108 << ErrorCodeShift) | ModuleId,
InvalidStoreData = (109 << ErrorCodeShift) | ModuleId,
InvalidOperationOnSpecialMii = (202 << ErrorCodeShift) | ModuleId,
PermissionDenied = (203 << ErrorCodeShift) | ModuleId,
TestModeNotEnabled = (204 << ErrorCodeShift) | ModuleId,
}
}

View file

@ -0,0 +1,266 @@
using Ryujinx.HLE.HOS.Services.Mii.Types;
using Ryujinx.HLE.HOS.Services.Settings;
using System;
namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
{
class DatabaseServiceImpl : IDatabaseService
{
private DatabaseImpl _database;
private DatabaseSessionMetadata _metadata;
private bool _isSystem;
public DatabaseServiceImpl(DatabaseImpl database, bool isSystem, SpecialMiiKeyCode miiKeyCode)
{
_database = database;
_metadata = _database.CreateSessionMetadata(miiKeyCode);
_isSystem = isSystem;
}
public bool IsDatabaseTestModeEnabled()
{
if (NxSettings.Settings.TryGetValue("mii!is_db_test_mode_enabled", out object isDatabaseTestModeEnabled))
{
return (bool)isDatabaseTestModeEnabled;
}
return false;
}
protected override bool IsUpdated(SourceFlag flag)
{
return _database.IsUpdated(_metadata, flag);
}
protected override bool IsFullDatabase()
{
return _database.IsFullDatabase();
}
protected override uint GetCount(SourceFlag flag)
{
return _database.GetCount(_metadata, flag);
}
protected override ResultCode Get(SourceFlag flag, out int count, Span<CharInfoElement> elements)
{
return _database.Get(_metadata, flag, out count, elements);
}
protected override ResultCode Get1(SourceFlag flag, out int count, Span<CharInfo> elements)
{
return _database.Get(_metadata, flag, out count, elements);
}
protected override ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
{
newCharInfo = default;
return _database.UpdateLatest(_metadata, oldCharInfo, flag, newCharInfo);
}
protected override ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo)
{
if (age > Age.All || gender > Gender.All || race > Race.All)
{
charInfo = default;
return ResultCode.InvalidArgument;
}
_database.BuildRandom(age, gender, race, out charInfo);
return ResultCode.Success;
}
protected override ResultCode BuildDefault(uint index, out CharInfo charInfo)
{
if (index >= DefaultMii.TableLength)
{
charInfo = default;
return ResultCode.InvalidArgument;
}
_database.BuildDefault(index, out charInfo);
return ResultCode.Success;
}
protected override ResultCode Get2(SourceFlag flag, out int count, Span<StoreDataElement> elements)
{
if (!_isSystem)
{
count = -1;
return ResultCode.PermissionDenied;
}
return _database.Get(_metadata, flag, out count, elements);
}
protected override ResultCode Get3(SourceFlag flag, out int count, Span<StoreData> elements)
{
if (!_isSystem)
{
count = -1;
return ResultCode.PermissionDenied;
}
return _database.Get(_metadata, flag, out count, elements);
}
protected override ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData)
{
newStoreData = default;
if (!_isSystem)
{
return ResultCode.PermissionDenied;
}
return _database.UpdateLatest(_metadata, oldStoreData, flag, newStoreData);
}
protected override ResultCode FindIndex(CreateId createId, bool isSpecial, out int index)
{
if (!_isSystem)
{
index = -1;
return ResultCode.PermissionDenied;
}
index = _database.FindIndex(createId, isSpecial);
return ResultCode.Success;
}
protected override ResultCode Move(CreateId createId, int newIndex)
{
if (!_isSystem)
{
return ResultCode.PermissionDenied;
}
if (newIndex > 0 && _database.GetCount(_metadata, SourceFlag.Database) > newIndex)
{
return _database.Move(_metadata, newIndex, createId);
}
return ResultCode.InvalidArgument;
}
protected override ResultCode AddOrReplace(StoreData storeData)
{
if (!_isSystem)
{
return ResultCode.PermissionDenied;
}
return _database.AddOrReplace(_metadata, storeData);
}
protected override ResultCode Delete(CreateId createId)
{
if (!_isSystem)
{
return ResultCode.PermissionDenied;
}
return _database.Delete(_metadata, createId);
}
protected override ResultCode DestroyFile()
{
if (!IsDatabaseTestModeEnabled())
{
return ResultCode.TestModeNotEnabled;
}
return _database.DestroyFile(_metadata);
}
protected override ResultCode DeleteFile()
{
if (!IsDatabaseTestModeEnabled())
{
return ResultCode.TestModeNotEnabled;
}
return _database.DeleteFile();
}
protected override ResultCode Format()
{
if (!IsDatabaseTestModeEnabled())
{
return ResultCode.TestModeNotEnabled;
}
_database.Format(_metadata);
return ResultCode.Success;
}
protected override ResultCode Import(ReadOnlySpan<byte> data)
{
if (!IsDatabaseTestModeEnabled())
{
return ResultCode.TestModeNotEnabled;
}
throw new NotImplementedException();
}
protected override ResultCode Export(Span<byte> data)
{
if (!IsDatabaseTestModeEnabled())
{
return ResultCode.TestModeNotEnabled;
}
throw new NotImplementedException();
}
protected override ResultCode IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase)
{
if (!_isSystem)
{
isBrokenDatabase = false;
return ResultCode.PermissionDenied;
}
isBrokenDatabase = _database.IsBrokenDatabaseWithClearFlag();
return ResultCode.Success;
}
protected override ResultCode GetIndex(CharInfo charInfo, out int index)
{
return _database.GetIndex(_metadata, charInfo, out index);
}
protected override void SetInterfaceVersion(uint interfaceVersion)
{
_database.SetInterfaceVersion(_metadata, interfaceVersion);
}
protected override ResultCode Convert(Ver3StoreData ver3StoreData, out CharInfo charInfo)
{
throw new NotImplementedException();
}
protected override ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo)
{
return _database.ConvertCoreDataToCharInfo(coreData, out charInfo);
}
protected override ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData)
{
return _database.ConvertCharInfoToCoreData(charInfo, out coreData);
}
}
}

View file

@ -0,0 +1,423 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Services.Mii.Types;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
{
abstract class IDatabaseService : IpcService
{
[Command(0)]
// IsUpdated(SourceFlag flag) -> bool
public ResultCode IsUpdated(ServiceCtx context)
{
SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32();
context.ResponseData.Write(IsUpdated(flag));
return ResultCode.Success;
}
[Command(1)]
// IsFullDatabase() -> bool
public ResultCode IsFullDatabase(ServiceCtx context)
{
context.ResponseData.Write(IsFullDatabase());
return ResultCode.Success;
}
[Command(2)]
// GetCount(SourceFlag flag) -> u32
public ResultCode GetCount(ServiceCtx context)
{
SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32();
context.ResponseData.Write(GetCount(flag));
return ResultCode.Success;
}
[Command(3)]
// Get(SourceFlag flag) -> (s32 count, buffer<nn::mii::CharInfoRawElement, 6>)
public ResultCode Get(ServiceCtx context)
{
SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32();
IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0];
Span<CharInfoElement> elementsSpan = CreateSpanFromBuffer<CharInfoElement>(context, outputBuffer, true);
ResultCode result = Get(flag, out int count, elementsSpan);
elementsSpan = elementsSpan.Slice(0, count);
context.ResponseData.Write(count);
WriteSpanToBuffer(context, outputBuffer, elementsSpan);
return result;
}
[Command(4)]
// Get1(SourceFlag flag) -> (s32 count, buffer<nn::mii::CharInfo, 6>)
public ResultCode Get1(ServiceCtx context)
{
SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32();
IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0];
Span<CharInfo> elementsSpan = CreateSpanFromBuffer<CharInfo>(context, outputBuffer, true);
ResultCode result = Get1(flag, out int count, elementsSpan);
elementsSpan = elementsSpan.Slice(0, count);
context.ResponseData.Write(count);
WriteSpanToBuffer(context, outputBuffer, elementsSpan);
return result;
}
[Command(5)]
// UpdateLatest(nn::mii::CharInfo old_char_info, SourceFlag flag) -> nn::mii::CharInfo
public ResultCode UpdateLatest(ServiceCtx context)
{
CharInfo oldCharInfo = context.RequestData.ReadStruct<CharInfo>();
SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32();
ResultCode result = UpdateLatest(oldCharInfo, flag, out CharInfo newCharInfo);
context.ResponseData.WriteStruct(newCharInfo);
return result;
}
[Command(6)]
// BuildRandom(Age age, Gender gender, Race race) -> nn::mii::CharInfo
public ResultCode BuildRandom(ServiceCtx context)
{
Age age = (Age)context.RequestData.ReadInt32();
Gender gender = (Gender)context.RequestData.ReadInt32();
Race race = (Race)context.RequestData.ReadInt32();
ResultCode result = BuildRandom(age, gender, race, out CharInfo charInfo);
context.ResponseData.WriteStruct(charInfo);
return result;
}
[Command(7)]
// BuildDefault(u32 index) -> nn::mii::CharInfoRaw
public ResultCode BuildDefault(ServiceCtx context)
{
uint index = context.RequestData.ReadUInt32();
ResultCode result = BuildDefault(index, out CharInfo charInfo);
context.ResponseData.WriteStruct(charInfo);
return result;
}
[Command(8)]
// Get2(SourceFlag flag) -> (u32 count, buffer<nn::mii::StoreDataElement, 6>)
public ResultCode Get2(ServiceCtx context)
{
SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32();
IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0];
Span<StoreDataElement> elementsSpan = CreateSpanFromBuffer<StoreDataElement>(context, outputBuffer, true);
ResultCode result = Get2(flag, out int count, elementsSpan);
elementsSpan = elementsSpan.Slice(0, count);
context.ResponseData.Write(count);
WriteSpanToBuffer(context, outputBuffer, elementsSpan);
return result;
}
[Command(9)]
// Get3(SourceFlag flag) -> (u32 count, buffer<nn::mii::StoreData, 6>)
public ResultCode Get3(ServiceCtx context)
{
SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32();
IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0];
Span<StoreData> elementsSpan = CreateSpanFromBuffer<StoreData>(context, outputBuffer, true);
ResultCode result = Get3(flag, out int count, elementsSpan);
elementsSpan = elementsSpan.Slice(0, count);
context.ResponseData.Write(count);
WriteSpanToBuffer(context, outputBuffer, elementsSpan);
return result;
}
[Command(10)]
// UpdateLatest1(nn::mii::StoreData old_store_data, SourceFlag flag) -> nn::mii::StoreData
public ResultCode UpdateLatest1(ServiceCtx context)
{
StoreData oldStoreData = context.RequestData.ReadStruct<StoreData>();
SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32();
ResultCode result = UpdateLatest1(oldStoreData, flag, out StoreData newStoreData);
context.ResponseData.WriteStruct(newStoreData);
return result;
}
[Command(11)]
// FindIndex(nn::mii::CreateId create_id, bool is_special) -> s32
public ResultCode FindIndex(ServiceCtx context)
{
CreateId createId = context.RequestData.ReadStruct<CreateId>();
bool isSpecial = context.RequestData.ReadBoolean();
ResultCode result = FindIndex(createId, isSpecial, out int index);
context.ResponseData.Write(index);
return result;
}
[Command(12)]
// Move(nn::mii::CreateId create_id, s32 new_index)
public ResultCode Move(ServiceCtx context)
{
CreateId createId = context.RequestData.ReadStruct<CreateId>();
int newIndex = context.RequestData.ReadInt32();
return Move(createId, newIndex);
}
[Command(13)]
// AddOrReplace(nn::mii::StoreData store_data)
public ResultCode AddOrReplace(ServiceCtx context)
{
StoreData storeData = context.RequestData.ReadStruct<StoreData>();
return AddOrReplace(storeData);
}
[Command(14)]
// Delete(nn::mii::CreateId create_id)
public ResultCode Delete(ServiceCtx context)
{
CreateId createId = context.RequestData.ReadStruct<CreateId>();
return Delete(createId);
}
[Command(15)]
// DestroyFile()
public ResultCode DestroyFile(ServiceCtx context)
{
return DestroyFile();
}
[Command(16)]
// DeleteFile()
public ResultCode DeleteFile(ServiceCtx context)
{
return DeleteFile();
}
[Command(17)]
// Format()
public ResultCode Format(ServiceCtx context)
{
return Format();
}
[Command(18)]
// Import(buffer<bytes, 5>)
public ResultCode Import(ServiceCtx context)
{
ReadOnlySpan<byte> data = CreateByteSpanFromBuffer(context, context.Request.SendBuff[0], false);
return Import(data);
}
[Command(19)]
// Export() -> buffer<bytes, 6>
public ResultCode Export(ServiceCtx context)
{
IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0];
Span<byte> data = CreateByteSpanFromBuffer(context, outputBuffer, true);
ResultCode result = Export(data);
context.Memory.WriteBytes(outputBuffer.Position, data.ToArray());
return result;
}
[Command(20)]
// IsBrokenDatabaseWithClearFlag() -> bool
public ResultCode IsBrokenDatabaseWithClearFlag(ServiceCtx context)
{
ResultCode result = IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase);
context.ResponseData.Write(isBrokenDatabase);
return result;
}
[Command(21)]
// GetIndex(nn::mii::CharInfo char_info) -> s32
public ResultCode GetIndex(ServiceCtx context)
{
CharInfo charInfo = context.RequestData.ReadStruct<CharInfo>();
ResultCode result = GetIndex(charInfo, out int index);
context.ResponseData.Write(index);
return result;
}
[Command(22)] // 5.0.0+
// SetInterfaceVersion(u32 version)
public ResultCode SetInterfaceVersion(ServiceCtx context)
{
uint interfaceVersion = context.RequestData.ReadUInt32();
SetInterfaceVersion(interfaceVersion);
return ResultCode.Success;
}
[Command(23)] // 5.0.0+
// Convert(nn::mii::Ver3StoreData ver3_store_data) -> nn::mii::CharInfo
public ResultCode Convert(ServiceCtx context)
{
Ver3StoreData ver3StoreData = context.RequestData.ReadStruct<Ver3StoreData>();
ResultCode result = Convert(ver3StoreData, out CharInfo charInfo);
context.ResponseData.WriteStruct(charInfo);
return result;
}
[Command(24)] // 7.0.0+
// ConvertCoreDataToCharInfo(nn::mii::CoreData core_data) -> nn::mii::CharInfo
public ResultCode ConvertCoreDataToCharInfo(ServiceCtx context)
{
CoreData coreData = context.RequestData.ReadStruct<CoreData>();
ResultCode result = ConvertCoreDataToCharInfo(coreData, out CharInfo charInfo);
context.ResponseData.WriteStruct(charInfo);
return result;
}
[Command(25)] // 7.0.0+
// ConvertCharInfoToCoreData(nn::mii::CharInfo char_info) -> nn::mii::CoreData
public ResultCode ConvertCharInfoToCoreData(ServiceCtx context)
{
CharInfo charInfo = context.RequestData.ReadStruct<CharInfo>();
ResultCode result = ConvertCharInfoToCoreData(charInfo, out CoreData coreData);
context.ResponseData.WriteStruct(coreData);
return result;
}
private Span<byte> CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput)
{
byte[] rawData;
if (isOutput)
{
rawData = new byte[ipcBuff.Size];
}
else
{
rawData = context.Memory.ReadBytes(ipcBuff.Position, ipcBuff.Size);
}
return new Span<byte>(rawData);
}
private Span<T> CreateSpanFromBuffer<T>(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput) where T: unmanaged
{
return MemoryMarshal.Cast<byte, T>(CreateByteSpanFromBuffer(context, ipcBuff, isOutput));
}
private void WriteSpanToBuffer<T>(ServiceCtx context, IpcBuffDesc ipcBuff, Span<T> span) where T: unmanaged
{
Span<byte> rawData = MemoryMarshal.Cast<T, byte>(span);
context.Memory.WriteBytes(ipcBuff.Position, rawData.ToArray());
}
protected abstract bool IsUpdated(SourceFlag flag);
protected abstract bool IsFullDatabase();
protected abstract uint GetCount(SourceFlag flag);
protected abstract ResultCode Get(SourceFlag flag, out int count, Span<CharInfoElement> elements);
protected abstract ResultCode Get1(SourceFlag flag, out int count, Span<CharInfo> elements);
protected abstract ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo);
protected abstract ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo);
protected abstract ResultCode BuildDefault(uint index, out CharInfo charInfo);
protected abstract ResultCode Get2(SourceFlag flag, out int count, Span<StoreDataElement> elements);
protected abstract ResultCode Get3(SourceFlag flag, out int count, Span<StoreData> elements);
protected abstract ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData);
protected abstract ResultCode FindIndex(CreateId createId, bool isSpecial, out int index);
protected abstract ResultCode Move(CreateId createId, int newIndex);
protected abstract ResultCode AddOrReplace(StoreData storeData);
protected abstract ResultCode Delete(CreateId createId);
protected abstract ResultCode DestroyFile();
protected abstract ResultCode DeleteFile();
protected abstract ResultCode Format();
protected abstract ResultCode Import(ReadOnlySpan<byte> data);
protected abstract ResultCode Export(Span<byte> data);
protected abstract ResultCode IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase);
protected abstract ResultCode GetIndex(CharInfo charInfo, out int index);
protected abstract void SetInterfaceVersion(uint interfaceVersion);
protected abstract ResultCode Convert(Ver3StoreData ver3StoreData, out CharInfo charInfo);
protected abstract ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo);
protected abstract ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData);
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum Age : uint
{
Young,
Normal,
Old,
All
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum BeardType : byte
{
None,
Goatee,
GoateeLong,
LionsManeLong,
LionsMane,
Full,
Min = 0,
Max = 5
}
}

View file

@ -0,0 +1,329 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x58)]
struct CharInfo : IStoredData<CharInfo>
{
public CreateId CreateId;
public Nickname Nickname;
public FontRegion FontRegion;
public byte FavoriteColor;
public Gender Gender;
public byte Height;
public byte Build;
public byte Type;
public byte RegionMove;
public FacelineType FacelineType;
public FacelineColor FacelineColor;
public FacelineWrinkle FacelineWrinkle;
public FacelineMake FacelineMake;
public HairType HairType;
public CommonColor HairColor;
public HairFlip HairFlip;
public EyeType EyeType;
public CommonColor EyeColor;
public byte EyeScale;
public byte EyeAspect;
public byte EyeRotate;
public byte EyeX;
public byte EyeY;
public EyebrowType EyebrowType;
public CommonColor EyebrowColor;
public byte EyebrowScale;
public byte EyebrowAspect;
public byte EyebrowRotate;
public byte EyebrowX;
public byte EyebrowY;
public NoseType NoseType;
public byte NoseScale;
public byte NoseY;
public MouthType MouthType;
public CommonColor MouthColor;
public byte MouthScale;
public byte MouthAspect;
public byte MouthY;
public CommonColor BeardColor;
public BeardType BeardType;
public MustacheType MustacheType;
public byte MustacheScale;
public byte MustacheY;
public GlassType GlassType;
public CommonColor GlassColor;
public byte GlassScale;
public byte GlassY;
public MoleType MoleType;
public byte MoleScale;
public byte MoleX;
public byte MoleY;
public byte Reserved;
byte IStoredData<CharInfo>.Type => Type;
CreateId IStoredData<CharInfo>.CreateId => CreateId;
public ResultCode InvalidData => ResultCode.InvalidCharInfo;
public bool IsValid()
{
return Verify() == 0;
}
public uint Verify()
{
if (!CreateId.IsValid) return 50;
if (!Nickname.IsValid()) return 51;
if ((byte)FontRegion > 3) return 23;
if (FavoriteColor > 11) return 22;
if (Gender > Gender.Max) return 24;
if ((sbyte)Height < 0) return 32;
if ((sbyte)Build < 0) return 3;
if (Type > 1) return 53;
if (RegionMove > 3) return 49;
if (FacelineType > FacelineType.Max) return 21;
if (FacelineColor > FacelineColor.Max) return 18;
if (FacelineWrinkle > FacelineWrinkle.Max) return 20;
if (FacelineMake > FacelineMake.Max) return 19;
if (HairType > HairType.Max) return 31;
if (HairColor > CommonColor.Max) return 29;
if (HairFlip > HairFlip.Max) return 30;
if (EyeType > EyeType.Max) return 8;
if (EyeColor > CommonColor.Max) return 5;
if (EyeScale > 7) return 7;
if (EyeAspect > 6) return 4;
if (EyeRotate > 7) return 6;
if (EyeX > 12) return 9;
if (EyeY > 18) return 10;
if (EyebrowType > EyebrowType.Max) return 15;
if (EyebrowColor > CommonColor.Max) return 12;
if (EyebrowScale > 8) return 14;
if (EyebrowAspect > 6) return 11;
if (EyebrowRotate > 11) return 13;
if (EyebrowX > 12) return 16;
if (EyebrowY - 3 > 15) return 17;
if (NoseType > NoseType.Max) return 47;
if (NoseScale > 8) return 46;
if (NoseY> 18) return 48;
if (MouthType > MouthType.Max) return 40;
if (MouthColor > CommonColor.Max) return 38;
if (MouthScale > 8) return 39;
if (MouthAspect > 6) return 37;
if (MouthY > 18) return 41;
if (BeardColor > CommonColor.Max) return 1;
if (BeardType > BeardType.Max) return 2;
if (MustacheType > MustacheType.Max) return 43;
if (MustacheScale > 8) return 42;
if (MustacheY > 16) return 44;
if (GlassType > GlassType.Max) return 27;
if (GlassColor > CommonColor.Max) return 25;
if (GlassScale > 7) return 26;
if (GlassY > 20) return 28;
if (MoleType > MoleType.Max) return 34;
if (MoleScale > 8) return 33;
if (MoleX > 16) return 35;
if (MoleY >= 31) return 36;
return 0;
}
public void SetFromStoreData(StoreData storeData)
{
Nickname = storeData.CoreData.Nickname;
CreateId = storeData.CreateId;
FontRegion = storeData.CoreData.FontRegion;
FavoriteColor = storeData.CoreData.FavoriteColor;
Gender = storeData.CoreData.Gender;
Height = storeData.CoreData.Height;
Build = storeData.CoreData.Build;
Type = storeData.CoreData.Type;
RegionMove = storeData.CoreData.RegionMove;
FacelineType = storeData.CoreData.FacelineType;
FacelineColor = storeData.CoreData.FacelineColor;
FacelineWrinkle = storeData.CoreData.FacelineWrinkle;
FacelineMake = storeData.CoreData.FacelineMake;
HairType = storeData.CoreData.HairType;
HairColor = storeData.CoreData.HairColor;
HairFlip = storeData.CoreData.HairFlip;
EyeType = storeData.CoreData.EyeType;
EyeColor = storeData.CoreData.EyeColor;
EyeScale = storeData.CoreData.EyeScale;
EyeAspect = storeData.CoreData.EyeAspect;
EyeRotate = storeData.CoreData.EyeRotate;
EyeX = storeData.CoreData.EyeX;
EyeY = storeData.CoreData.EyeY;
EyebrowType = storeData.CoreData.EyebrowType;
EyebrowColor = storeData.CoreData.EyebrowColor;
EyebrowScale = storeData.CoreData.EyebrowScale;
EyebrowAspect = storeData.CoreData.EyebrowAspect;
EyebrowRotate = storeData.CoreData.EyebrowRotate;
EyebrowX = storeData.CoreData.EyebrowX;
EyebrowY = storeData.CoreData.EyebrowY;
NoseType = storeData.CoreData.NoseType;
NoseScale = storeData.CoreData.NoseScale;
NoseY = storeData.CoreData.NoseY;
MouthType = storeData.CoreData.MouthType;
MouthColor = storeData.CoreData.MouthColor;
MouthScale = storeData.CoreData.MouthScale;
MouthAspect = storeData.CoreData.MouthAspect;
MouthY = storeData.CoreData.MouthY;
BeardColor = storeData.CoreData.BeardColor;
BeardType = storeData.CoreData.BeardType;
MustacheType = storeData.CoreData.MustacheType;
MustacheScale = storeData.CoreData.MustacheScale;
MustacheY = storeData.CoreData.MustacheY;
GlassType = storeData.CoreData.GlassType;
GlassColor = storeData.CoreData.GlassColor;
GlassScale = storeData.CoreData.GlassScale;
GlassY = storeData.CoreData.GlassY;
MoleType = storeData.CoreData.MoleType;
MoleScale = storeData.CoreData.MoleScale;
MoleX = storeData.CoreData.MoleX;
MoleY = storeData.CoreData.MoleY;
Reserved = 0;
}
public void SetSource(Source source)
{
// Only implemented for Element variants.
}
public static bool operator ==(CharInfo x, CharInfo y)
{
return x.Equals(y);
}
public static bool operator !=(CharInfo x, CharInfo y)
{
return !x.Equals(y);
}
public override bool Equals(object obj)
{
return obj is CharInfo charInfo && Equals(charInfo);
}
public bool Equals(CharInfo cmpObj)
{
if (!cmpObj.IsValid())
{
return false;
}
bool result = true;
result &= Nickname == cmpObj.Nickname;
result &= CreateId == cmpObj.CreateId;
result &= FontRegion == cmpObj.FontRegion;
result &= FavoriteColor == cmpObj.FavoriteColor;
result &= Gender == cmpObj.Gender;
result &= Height == cmpObj.Height;
result &= Build == cmpObj.Build;
result &= Type == cmpObj.Type;
result &= RegionMove == cmpObj.RegionMove;
result &= FacelineType == cmpObj.FacelineType;
result &= FacelineColor == cmpObj.FacelineColor;
result &= FacelineWrinkle == cmpObj.FacelineWrinkle;
result &= FacelineMake == cmpObj.FacelineMake;
result &= HairType == cmpObj.HairType;
result &= HairColor == cmpObj.HairColor;
result &= HairFlip == cmpObj.HairFlip;
result &= EyeType == cmpObj.EyeType;
result &= EyeColor == cmpObj.EyeColor;
result &= EyeScale == cmpObj.EyeScale;
result &= EyeAspect == cmpObj.EyeAspect;
result &= EyeRotate == cmpObj.EyeRotate;
result &= EyeX == cmpObj.EyeX;
result &= EyeY == cmpObj.EyeY;
result &= EyebrowType == cmpObj.EyebrowType;
result &= EyebrowColor == cmpObj.EyebrowColor;
result &= EyebrowScale == cmpObj.EyebrowScale;
result &= EyebrowAspect == cmpObj.EyebrowAspect;
result &= EyebrowRotate == cmpObj.EyebrowRotate;
result &= EyebrowX == cmpObj.EyebrowX;
result &= EyebrowY == cmpObj.EyebrowY;
result &= NoseType == cmpObj.NoseType;
result &= NoseScale == cmpObj.NoseScale;
result &= NoseY == cmpObj.NoseY;
result &= MouthType == cmpObj.MouthType;
result &= MouthColor == cmpObj.MouthColor;
result &= MouthScale == cmpObj.MouthScale;
result &= MouthAspect == cmpObj.MouthAspect;
result &= MouthY == cmpObj.MouthY;
result &= BeardColor == cmpObj.BeardColor;
result &= BeardType == cmpObj.BeardType;
result &= MustacheType == cmpObj.MustacheType;
result &= MustacheScale == cmpObj.MustacheScale;
result &= MustacheY == cmpObj.MustacheY;
result &= GlassType == cmpObj.GlassType;
result &= GlassColor == cmpObj.GlassColor;
result &= GlassScale == cmpObj.GlassScale;
result &= GlassY == cmpObj.GlassY;
result &= MoleType == cmpObj.MoleType;
result &= MoleScale == cmpObj.MoleScale;
result &= MoleX == cmpObj.MoleX;
result &= MoleY == cmpObj.MoleY;
return result;
}
public override int GetHashCode()
{
HashCode hashCode = new HashCode();
hashCode.Add(Nickname);
hashCode.Add(CreateId);
hashCode.Add(FontRegion);
hashCode.Add(FavoriteColor);
hashCode.Add(Gender);
hashCode.Add(Height);
hashCode.Add(Build);
hashCode.Add(Type);
hashCode.Add(RegionMove);
hashCode.Add(FacelineType);
hashCode.Add(FacelineColor);
hashCode.Add(FacelineWrinkle);
hashCode.Add(FacelineMake);
hashCode.Add(HairType);
hashCode.Add(HairColor);
hashCode.Add(HairFlip);
hashCode.Add(EyeType);
hashCode.Add(EyeColor);
hashCode.Add(EyeScale);
hashCode.Add(EyeAspect);
hashCode.Add(EyeRotate);
hashCode.Add(EyeX);
hashCode.Add(EyeY);
hashCode.Add(EyebrowType);
hashCode.Add(EyebrowColor);
hashCode.Add(EyebrowScale);
hashCode.Add(EyebrowAspect);
hashCode.Add(EyebrowRotate);
hashCode.Add(EyebrowX);
hashCode.Add(EyebrowY);
hashCode.Add(NoseType);
hashCode.Add(NoseScale);
hashCode.Add(NoseY);
hashCode.Add(MouthType);
hashCode.Add(MouthColor);
hashCode.Add(MouthScale);
hashCode.Add(MouthAspect);
hashCode.Add(MouthY);
hashCode.Add(BeardColor);
hashCode.Add(BeardType);
hashCode.Add(MustacheType);
hashCode.Add(MustacheScale);
hashCode.Add(MustacheY);
hashCode.Add(GlassType);
hashCode.Add(GlassColor);
hashCode.Add(GlassScale);
hashCode.Add(GlassY);
hashCode.Add(MoleType);
hashCode.Add(MoleScale);
hashCode.Add(MoleX);
hashCode.Add(MoleY);
return hashCode.ToHashCode();
}
}
}

View file

@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x5C)]
struct CharInfoElement : IElement
{
public CharInfo CharInfo;
public Source Source;
public void SetFromStoreData(StoreData storeData)
{
CharInfo.SetFromStoreData(storeData);
}
public void SetSource(Source source)
{
Source = source;
}
}
}

View file

@ -0,0 +1,9 @@

namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum CommonColor : byte
{
Min = 0,
Max = 99
}
}

View file

@ -0,0 +1,911 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Ryujinx.HLE.HOS.Services.Mii.Types.RandomMiiConstants;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)]
struct CoreData : IEquatable<CoreData>
{
public const int Size = 0x30;
private byte _storage;
public Span<byte> Storage => MemoryMarshal.CreateSpan(ref _storage, Size);
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x18)]
public struct ElementInfo
{
public int ByteOffset;
public int BitOffset;
public int BitWidth;
public int MinValue;
public int MaxValue;
public int Unknown;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetValue(ElementInfoIndex index)
{
ElementInfo info = ElementInfos[(int)index];
return ((Storage[info.ByteOffset] >> info.BitOffset) & ~(-1 << info.BitWidth)) + info.MinValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetValue(ElementInfoIndex index, int value)
{
ElementInfo info = ElementInfos[(int)index];
int newValue = Storage[info.ByteOffset] & ~(~(-1 << info.BitWidth) << info.BitOffset) | (((value - info.MinValue) & ~(-1 << info.BitWidth)) << info.BitOffset);
Storage[info.ByteOffset] = (byte)newValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsElementValid(ElementInfoIndex index)
{
ElementInfo info = ElementInfos[(int)index];
int value = GetValue(index);
return value >= info.MinValue && value <= info.MaxValue;
}
public bool IsValid(bool acceptEmptyNickname = false)
{
if (!Nickname.IsValid() || (!acceptEmptyNickname && Nickname.IsEmpty()))
{
return false;
}
for (int i = 0; i < ElementInfos.Length; i++)
{
if (!IsElementValid((ElementInfoIndex)i))
{
return false;
}
}
return true;
}
public void SetDefault()
{
Storage.Fill(0);
Nickname = Nickname.Default;
}
public HairType HairType
{
get => (HairType)GetValue(ElementInfoIndex.HairType);
set => SetValue(ElementInfoIndex.HairType, (int)value);
}
public byte Height
{
get => (byte)GetValue(ElementInfoIndex.Height);
set => SetValue(ElementInfoIndex.Height, value);
}
public MoleType MoleType
{
get => (MoleType)GetValue(ElementInfoIndex.MoleType);
set => SetValue(ElementInfoIndex.MoleType, (byte)value);
}
public byte Build
{
get => (byte)GetValue(ElementInfoIndex.Build);
set => SetValue(ElementInfoIndex.Build, value);
}
public HairFlip HairFlip
{
get => (HairFlip)GetValue(ElementInfoIndex.HairFlip);
set => SetValue(ElementInfoIndex.HairFlip, (byte)value);
}
public CommonColor HairColor
{
get => (CommonColor)GetValue(ElementInfoIndex.HairColor);
set => SetValue(ElementInfoIndex.HairColor, (int)value);
}
public byte Type
{
get => (byte)GetValue(ElementInfoIndex.Type);
set => SetValue(ElementInfoIndex.Type, value);
}
public CommonColor EyeColor
{
get => (CommonColor)GetValue(ElementInfoIndex.EyeColor);
set => SetValue(ElementInfoIndex.EyeColor, (int)value);
}
public Gender Gender
{
get => (Gender)GetValue(ElementInfoIndex.Gender);
set => SetValue(ElementInfoIndex.Gender, (int)value);
}
public CommonColor EyebrowColor
{
get => (CommonColor)GetValue(ElementInfoIndex.EyebrowColor);
set => SetValue(ElementInfoIndex.EyebrowColor, (int)value);
}
public CommonColor MouthColor
{
get => (CommonColor)GetValue(ElementInfoIndex.MouthColor);
set => SetValue(ElementInfoIndex.MouthColor, (int)value);
}
public CommonColor BeardColor
{
get => (CommonColor)GetValue(ElementInfoIndex.BeardColor);
set => SetValue(ElementInfoIndex.BeardColor, (byte)value);
}
public CommonColor GlassColor
{
get => (CommonColor)GetValue(ElementInfoIndex.GlassColor);
set => SetValue(ElementInfoIndex.GlassColor, (int)value);
}
public EyeType EyeType
{
get => (EyeType)GetValue(ElementInfoIndex.EyeType);
set => SetValue(ElementInfoIndex.EyeType, (int)value);
}
public byte RegionMove
{
get => (byte)GetValue(ElementInfoIndex.RegionMove);
set => SetValue(ElementInfoIndex.RegionMove, value);
}
public MouthType MouthType
{
get => (MouthType)GetValue(ElementInfoIndex.MouthType);
set => SetValue(ElementInfoIndex.MouthType, (int)value);
}
public FontRegion FontRegion
{
get => (FontRegion)GetValue(ElementInfoIndex.FontRegion);
set => SetValue(ElementInfoIndex.FontRegion, (byte)value);
}
public byte EyeY
{
get => (byte)GetValue(ElementInfoIndex.EyeY);
set => SetValue(ElementInfoIndex.EyeY, value);
}
public byte GlassScale
{
get => (byte)GetValue(ElementInfoIndex.GlassScale);
set => SetValue(ElementInfoIndex.GlassScale, value);
}
public EyebrowType EyebrowType
{
get => (EyebrowType)GetValue(ElementInfoIndex.EyebrowType);
set => SetValue(ElementInfoIndex.EyebrowType, (int)value);
}
public MustacheType MustacheType
{
get => (MustacheType)GetValue(ElementInfoIndex.MustacheType);
set => SetValue(ElementInfoIndex.MustacheType, (int)value);
}
public NoseType NoseType
{
get => (NoseType)GetValue(ElementInfoIndex.NoseType);
set => SetValue(ElementInfoIndex.NoseType, (int)value);
}
public BeardType BeardType
{
get => (BeardType)GetValue(ElementInfoIndex.BeardType);
set => SetValue(ElementInfoIndex.BeardType, (int)value);
}
public byte NoseY
{
get => (byte)GetValue(ElementInfoIndex.NoseY);
set => SetValue(ElementInfoIndex.NoseY, value);
}
public byte MouthAspect
{
get => (byte)GetValue(ElementInfoIndex.MouthAspect);
set => SetValue(ElementInfoIndex.MouthAspect, value);
}
public byte MouthY
{
get => (byte)GetValue(ElementInfoIndex.MouthY);
set => SetValue(ElementInfoIndex.MouthY, value);
}
public byte EyebrowAspect
{
get => (byte)GetValue(ElementInfoIndex.EyebrowAspect);
set => SetValue(ElementInfoIndex.EyebrowAspect, value);
}
public byte MustacheY
{
get => (byte)GetValue(ElementInfoIndex.MustacheY);
set => SetValue(ElementInfoIndex.MustacheY, value);
}
public byte EyeRotate
{
get => (byte)GetValue(ElementInfoIndex.EyeRotate);
set => SetValue(ElementInfoIndex.EyeRotate, value);
}
public byte GlassY
{
get => (byte)GetValue(ElementInfoIndex.GlassY);
set => SetValue(ElementInfoIndex.GlassY, value);
}
public byte EyeAspect
{
get => (byte)GetValue(ElementInfoIndex.EyeAspect);
set => SetValue(ElementInfoIndex.EyeAspect, value);
}
public byte MoleX
{
get => (byte)GetValue(ElementInfoIndex.MoleX);
set => SetValue(ElementInfoIndex.MoleX, value);
}
public byte EyeScale
{
get => (byte)GetValue(ElementInfoIndex.EyeScale);
set => SetValue(ElementInfoIndex.EyeScale, value);
}
public byte MoleY
{
get => (byte)GetValue(ElementInfoIndex.MoleY);
set => SetValue(ElementInfoIndex.MoleY, value);
}
public GlassType GlassType
{
get => (GlassType)GetValue(ElementInfoIndex.GlassType);
set => SetValue(ElementInfoIndex.GlassType, (int)value);
}
public byte FavoriteColor
{
get => (byte)GetValue(ElementInfoIndex.FavoriteColor);
set => SetValue(ElementInfoIndex.FavoriteColor, value);
}
public FacelineType FacelineType
{
get => (FacelineType)GetValue(ElementInfoIndex.FacelineType);
set => SetValue(ElementInfoIndex.FacelineType, (int)value);
}
public FacelineColor FacelineColor
{
get => (FacelineColor)GetValue(ElementInfoIndex.FacelineColor);
set => SetValue(ElementInfoIndex.FacelineColor, (int)value);
}
public FacelineWrinkle FacelineWrinkle
{
get => (FacelineWrinkle)GetValue(ElementInfoIndex.FacelineWrinkle);
set => SetValue(ElementInfoIndex.FacelineWrinkle, (int)value);
}
public FacelineMake FacelineMake
{
get => (FacelineMake)GetValue(ElementInfoIndex.FacelineMake);
set => SetValue(ElementInfoIndex.FacelineMake, (int)value);
}
public byte EyeX
{
get => (byte)GetValue(ElementInfoIndex.EyeX);
set => SetValue(ElementInfoIndex.EyeX, value);
}
public byte EyebrowScale
{
get => (byte)GetValue(ElementInfoIndex.EyebrowScale);
set => SetValue(ElementInfoIndex.EyebrowScale, value);
}
public byte EyebrowRotate
{
get => (byte)GetValue(ElementInfoIndex.EyebrowRotate);
set => SetValue(ElementInfoIndex.EyebrowRotate, value);
}
public byte EyebrowX
{
get => (byte)GetValue(ElementInfoIndex.EyebrowX);
set => SetValue(ElementInfoIndex.EyebrowX, value);
}
public byte EyebrowY
{
get => (byte)GetValue(ElementInfoIndex.EyebrowY);
set => SetValue(ElementInfoIndex.EyebrowY, value);
}
public byte NoseScale
{
get => (byte)GetValue(ElementInfoIndex.NoseScale);
set => SetValue(ElementInfoIndex.NoseScale, value);
}
public byte MouthScale
{
get => (byte)GetValue(ElementInfoIndex.MouthScale);
set => SetValue(ElementInfoIndex.MouthScale, value);
}
public byte MustacheScale
{
get => (byte)GetValue(ElementInfoIndex.MustacheScale);
set => SetValue(ElementInfoIndex.MustacheScale, value);
}
public byte MoleScale
{
get => (byte)GetValue(ElementInfoIndex.MoleScale);
set => SetValue(ElementInfoIndex.MoleScale, value);
}
public Span<byte> GetNicknameStorage()
{
return Storage.Slice(0x1c);
}
public Nickname Nickname
{
get => Nickname.FromBytes(GetNicknameStorage());
set => value.Raw.Slice(0, 20).CopyTo(GetNicknameStorage());
}
public static CoreData BuildRandom(UtilityImpl utilImpl, Age age, Gender gender, Race race)
{
CoreData coreData = new CoreData();
coreData.SetDefault();
if (gender == Types.Gender.All)
{
gender = (Gender)utilImpl.GetRandom((int)gender);
}
if (age == Age.All)
{
int ageDecade = utilImpl.GetRandom(10);
if (ageDecade >= 8)
{
age = Age.Old;
}
else if (ageDecade >= 4)
{
age = Age.Normal;
}
else
{
age = Age.Young;
}
}
if (race == Race.All)
{
int raceTempValue = utilImpl.GetRandom(10);
if (raceTempValue >= 8)
{
race = Race.Black;
}
else if (raceTempValue >= 4)
{
race = Race.White;
}
else
{
race = Race.Asian;
}
}
int axisY = 0;
if (gender == Types.Gender.Female && age == Age.Young)
{
axisY = utilImpl.GetRandom(3);
}
int indexFor4 = 3 * (int)age + 9 * (int)gender + (int)race;
var facelineTypeInfo = RandomMiiFacelineArray[indexFor4];
var facelineColorInfo = RandomMiiFacelineColorArray[3 * (int)gender + (int)race];
var facelineWrinkleInfo = RandomMiiFacelineWrinkleArray[indexFor4];
var facelineMakeInfo = RandomMiiFacelineMakeArray[indexFor4];
var hairTypeInfo = RandomMiiHairTypeArray[indexFor4];
var hairColorInfo = RandomMiiHairColorArray[3 * (int)race + (int)age];
var eyeTypeInfo = RandomMiiEyeTypeArray[indexFor4];
var eyeColorInfo = RandomMiiEyeColorArray[(int)race];
var eyebrowTypeInfo = RandomMiiEyebrowTypeArray[indexFor4];
var noseTypeInfo = RandomMiiNoseTypeArray[indexFor4];
var mouthTypeInfo = RandomMiiMouthTypeArray[indexFor4];
var glassTypeInfo = RandomMiiGlassTypeArray[(int)age];
// Faceline
coreData.FacelineType = (FacelineType)facelineTypeInfo.Values[utilImpl.GetRandom(facelineTypeInfo.ValuesCount)];
coreData.FacelineColor = (FacelineColor)Helper.Ver3FacelineColorTable[facelineColorInfo.Values[utilImpl.GetRandom(facelineColorInfo.ValuesCount)]];
coreData.FacelineWrinkle = (FacelineWrinkle)facelineWrinkleInfo.Values[utilImpl.GetRandom(facelineWrinkleInfo.ValuesCount)];
coreData.FacelineMake = (FacelineMake)facelineMakeInfo.Values[utilImpl.GetRandom(facelineMakeInfo.ValuesCount)];
// Hair
coreData.HairType = (HairType)hairTypeInfo.Values[utilImpl.GetRandom(hairTypeInfo.ValuesCount)];
coreData.HairColor = (CommonColor)Helper.Ver3HairColorTable[hairColorInfo.Values[utilImpl.GetRandom(hairColorInfo.ValuesCount)]];
coreData.HairFlip = (HairFlip)utilImpl.GetRandom((int)HairFlip.Max + 1);
// Eye
coreData.EyeType = (EyeType)eyeTypeInfo.Values[utilImpl.GetRandom(eyeTypeInfo.ValuesCount)];
int eyeRotateKey1 = gender != Types.Gender.Male ? 4 : 2;
int eyeRotateKey2 = gender != Types.Gender.Male ? 3 : 4;
byte eyeRotateOffset = (byte)(32 - EyeRotateTable[eyeRotateKey1] + eyeRotateKey2);
byte eyeRotate = (byte)(32 - EyeRotateTable[(int)coreData.EyeType]);
coreData.EyeColor = (CommonColor)Helper.Ver3EyeColorTable[eyeColorInfo.Values[utilImpl.GetRandom(eyeColorInfo.ValuesCount)]];
coreData.EyeScale = 4;
coreData.EyeAspect = 3;
coreData.EyeRotate = (byte)(eyeRotateOffset - eyeRotate);
coreData.EyeX = 2;
coreData.EyeY = (byte)(axisY + 12);
// Eyebrow
coreData.EyebrowType = (EyebrowType)eyebrowTypeInfo.Values[utilImpl.GetRandom(eyebrowTypeInfo.ValuesCount)];
int eyebrowRotateKey = race == Race.Asian ? 6 : 0;
int eyebrowY = race == Race.Asian ? 9 : 10;
byte eyebrowRotateOffset = (byte)(32 - EyebrowRotateTable[eyebrowRotateKey] + 6);
byte eyebrowRotate = (byte)(32 - EyebrowRotateTable[(int)coreData.EyebrowType]);
coreData.EyebrowColor = coreData.HairColor;
coreData.EyebrowScale = 4;
coreData.EyebrowAspect = 3;
coreData.EyebrowRotate = (byte)(eyebrowRotateOffset - eyebrowRotate);
coreData.EyebrowX = 2;
coreData.EyebrowY = (byte)(axisY + eyebrowY);
// Nose
int noseScale = gender == Types.Gender.Female ? 3 : 4;
coreData.NoseType = (NoseType)noseTypeInfo.Values[utilImpl.GetRandom(noseTypeInfo.ValuesCount)];
coreData.NoseScale = (byte)noseScale;
coreData.NoseY = (byte)(axisY + 9);
// Mouth
int mouthColor = gender == Types.Gender.Female ? utilImpl.GetRandom(0, 4) : 0;
coreData.MouthType = (MouthType)mouthTypeInfo.Values[utilImpl.GetRandom(mouthTypeInfo.ValuesCount)];
coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[mouthColor];
coreData.MouthScale = 4;
coreData.MouthAspect = 3;
coreData.MouthY = (byte)(axisY + 13);
// Beard & Mustache
coreData.BeardColor = coreData.HairColor;
coreData.MustacheScale = 4;
if (gender == Types.Gender.Male && age != Age.Young && utilImpl.GetRandom(10) < 2)
{
BeardAndMustacheFlag mustacheAndBeardFlag = (BeardAndMustacheFlag)utilImpl.GetRandom(3);
BeardType beardType = BeardType.None;
MustacheType mustacheType = MustacheType.None;
if ((mustacheAndBeardFlag & BeardAndMustacheFlag.Beard) == BeardAndMustacheFlag.Beard)
{
beardType = (BeardType)utilImpl.GetRandom((int)BeardType.Goatee, (int)BeardType.Full);
}
if ((mustacheAndBeardFlag & BeardAndMustacheFlag.Mustache) == BeardAndMustacheFlag.Mustache)
{
mustacheType = (MustacheType)utilImpl.GetRandom((int)MustacheType.Walrus, (int)MustacheType.Toothbrush);
}
coreData.MustacheType = mustacheType;
coreData.BeardType = beardType;
coreData.MustacheY = 10;
}
else
{
coreData.MustacheType = MustacheType.None;
coreData.BeardType = BeardType.None;
coreData.MustacheY = (byte)(axisY + 10);
}
// Glass
int glassTypeStart = utilImpl.GetRandom(100);
GlassType glassType = GlassType.None;
while (glassTypeStart < glassTypeInfo.Values[(int)glassType])
{
glassType++;
if ((int)glassType >= glassTypeInfo.ValuesCount)
{
throw new InvalidOperationException("glassTypeStart shouldn't exceed glassTypeInfo.ValuesCount");
}
}
coreData.GlassType = glassType;
coreData.GlassColor = (CommonColor)Helper.Ver3GlassColorTable[0];
coreData.GlassScale = 4;
coreData.GlassY = (byte)(axisY + 10);
// Mole
coreData.MoleType = 0;
coreData.MoleScale = 4;
coreData.MoleX = 2;
coreData.MoleY = 20;
// Body sizing
coreData.Height = 64;
coreData.Build = 64;
// Misc
coreData.Nickname = Nickname.Default;
coreData.Gender = gender;
coreData.FavoriteColor = (byte)utilImpl.GetRandom(0, 11);
coreData.RegionMove = 0;
coreData.FontRegion = 0;
coreData.Type = 0;
return coreData;
}
public void SetFromCharInfo(CharInfo charInfo)
{
Nickname = charInfo.Nickname;
FontRegion = charInfo.FontRegion;
FavoriteColor = charInfo.FavoriteColor;
Gender = (Gender)charInfo.Gender;
Height = charInfo.Height;
Build = charInfo.Build;
Type = charInfo.Type;
RegionMove = charInfo.RegionMove;
FacelineType = charInfo.FacelineType;
FacelineColor = charInfo.FacelineColor;
FacelineWrinkle = charInfo.FacelineWrinkle;
FacelineMake = charInfo.FacelineMake;
HairType = charInfo.HairType;
HairColor = charInfo.HairColor;
HairFlip = charInfo.HairFlip;
EyeType = charInfo.EyeType;
EyeColor = charInfo.EyeColor;
EyeScale = charInfo.EyeScale;
EyeAspect = charInfo.EyeAspect;
EyeRotate = charInfo.EyeRotate;
EyeX = charInfo.EyeX;
EyeY = charInfo.EyeY;
EyebrowType = charInfo.EyebrowType;
EyebrowColor = charInfo.EyebrowColor;
EyebrowScale = charInfo.EyebrowScale;
EyebrowAspect = charInfo.EyebrowAspect;
EyebrowRotate = charInfo.EyebrowRotate;
EyebrowX = charInfo.EyebrowX;
EyebrowY = charInfo.EyebrowY;
NoseType = charInfo.NoseType;
NoseScale = charInfo.NoseScale;
NoseY = charInfo.NoseY;
MouthType = charInfo.MouthType;
MouthColor = charInfo.MouthColor;
MouthScale = charInfo.MouthScale;
MouthAspect = charInfo.MouthAspect;
MouthY = charInfo.MouthY;
BeardColor = charInfo.BeardColor;
BeardType = charInfo.BeardType;
MustacheType = charInfo.MustacheType;
MustacheScale = charInfo.MustacheScale;
MustacheY = charInfo.MustacheY;
GlassType = charInfo.GlassType;
GlassColor = charInfo.GlassColor;
GlassScale = charInfo.GlassScale;
GlassY = charInfo.GlassY;
MoleType = charInfo.MoleType;
MoleScale = charInfo.MoleScale;
MoleX = charInfo.MoleX;
MoleY = charInfo.MoleY;
}
public static bool operator ==(CoreData x, CoreData y)
{
return x.Equals(y);
}
public static bool operator !=(CoreData x, CoreData y)
{
return !x.Equals(y);
}
public override bool Equals(object obj)
{
return obj is CoreData coreData && Equals(coreData);
}
public bool Equals(CoreData cmpObj)
{
if (!cmpObj.IsValid())
{
return false;
}
bool result = true;
result &= Nickname == cmpObj.Nickname;
result &= FontRegion == cmpObj.FontRegion;
result &= FavoriteColor == cmpObj.FavoriteColor;
result &= Gender == cmpObj.Gender;
result &= Height == cmpObj.Height;
result &= Build == cmpObj.Build;
result &= Type == cmpObj.Type;
result &= RegionMove == cmpObj.RegionMove;
result &= FacelineType == cmpObj.FacelineType;
result &= FacelineColor == cmpObj.FacelineColor;
result &= FacelineWrinkle == cmpObj.FacelineWrinkle;
result &= FacelineMake == cmpObj.FacelineMake;
result &= HairType == cmpObj.HairType;
result &= HairColor == cmpObj.HairColor;
result &= HairFlip == cmpObj.HairFlip;
result &= EyeType == cmpObj.EyeType;
result &= EyeColor == cmpObj.EyeColor;
result &= EyeScale == cmpObj.EyeScale;
result &= EyeAspect == cmpObj.EyeAspect;
result &= EyeRotate == cmpObj.EyeRotate;
result &= EyeX == cmpObj.EyeX;
result &= EyeY == cmpObj.EyeY;
result &= EyebrowType == cmpObj.EyebrowType;
result &= EyebrowColor == cmpObj.EyebrowColor;
result &= EyebrowScale == cmpObj.EyebrowScale;
result &= EyebrowAspect == cmpObj.EyebrowAspect;
result &= EyebrowRotate == cmpObj.EyebrowRotate;
result &= EyebrowX == cmpObj.EyebrowX;
result &= EyebrowY == cmpObj.EyebrowY;
result &= NoseType == cmpObj.NoseType;
result &= NoseScale == cmpObj.NoseScale;
result &= NoseY == cmpObj.NoseY;
result &= MouthType == cmpObj.MouthType;
result &= MouthColor == cmpObj.MouthColor;
result &= MouthScale == cmpObj.MouthScale;
result &= MouthAspect == cmpObj.MouthAspect;
result &= MouthY == cmpObj.MouthY;
result &= BeardColor == cmpObj.BeardColor;
result &= BeardType == cmpObj.BeardType;
result &= MustacheType == cmpObj.MustacheType;
result &= MustacheScale == cmpObj.MustacheScale;
result &= MustacheY == cmpObj.MustacheY;
result &= GlassType == cmpObj.GlassType;
result &= GlassColor == cmpObj.GlassColor;
result &= GlassScale == cmpObj.GlassScale;
result &= GlassY == cmpObj.GlassY;
result &= MoleType == cmpObj.MoleType;
result &= MoleScale == cmpObj.MoleScale;
result &= MoleX == cmpObj.MoleX;
result &= MoleY == cmpObj.MoleY;
return result;
}
public override int GetHashCode()
{
HashCode hashCode = new HashCode();
hashCode.Add(Nickname);
hashCode.Add(FontRegion);
hashCode.Add(FavoriteColor);
hashCode.Add(Gender);
hashCode.Add(Height);
hashCode.Add(Build);
hashCode.Add(Type);
hashCode.Add(RegionMove);
hashCode.Add(FacelineType);
hashCode.Add(FacelineColor);
hashCode.Add(FacelineWrinkle);
hashCode.Add(FacelineMake);
hashCode.Add(HairType);
hashCode.Add(HairColor);
hashCode.Add(HairFlip);
hashCode.Add(EyeType);
hashCode.Add(EyeColor);
hashCode.Add(EyeScale);
hashCode.Add(EyeAspect);
hashCode.Add(EyeRotate);
hashCode.Add(EyeX);
hashCode.Add(EyeY);
hashCode.Add(EyebrowType);
hashCode.Add(EyebrowColor);
hashCode.Add(EyebrowScale);
hashCode.Add(EyebrowAspect);
hashCode.Add(EyebrowRotate);
hashCode.Add(EyebrowX);
hashCode.Add(EyebrowY);
hashCode.Add(NoseType);
hashCode.Add(NoseScale);
hashCode.Add(NoseY);
hashCode.Add(MouthType);
hashCode.Add(MouthColor);
hashCode.Add(MouthScale);
hashCode.Add(MouthAspect);
hashCode.Add(MouthY);
hashCode.Add(BeardColor);
hashCode.Add(BeardType);
hashCode.Add(MustacheType);
hashCode.Add(MustacheScale);
hashCode.Add(MustacheY);
hashCode.Add(GlassType);
hashCode.Add(GlassColor);
hashCode.Add(GlassScale);
hashCode.Add(GlassY);
hashCode.Add(MoleType);
hashCode.Add(MoleScale);
hashCode.Add(MoleX);
hashCode.Add(MoleY);
return hashCode.ToHashCode();
}
private static ReadOnlySpan<ElementInfo> ElementInfos => MemoryMarshal.Cast<byte, ElementInfo>(ElementInfoArray);
private enum ElementInfoIndex : int
{
HairType,
Height,
MoleType,
Build,
HairFlip,
HairColor,
Type,
EyeColor,
Gender,
EyebrowColor,
MouthColor,
BeardColor,
GlassColor,
EyeType,
RegionMove,
MouthType,
FontRegion,
EyeY,
GlassScale,
EyebrowType,
MustacheType,
NoseType,
BeardType,
NoseY,
MouthAspect,
MouthY,
EyebrowAspect,
MustacheY,
EyeRotate,
GlassY,
EyeAspect,
MoleX,
EyeScale,
MoleY,
GlassType,
FavoriteColor,
FacelineType,
FacelineColor,
FacelineWrinkle,
FacelineMake,
EyeX,
EyebrowScale,
EyebrowRotate,
EyebrowX,
EyebrowY,
NoseScale,
MouthScale,
MustacheScale,
MoleScale
}
#region "Element Info Array"
private static ReadOnlySpan<byte> ElementInfoArray => new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x83, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x11, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x15, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x19, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x1a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x1b, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00
};
#endregion
}
}

View file

@ -0,0 +1,46 @@
using Ryujinx.HLE.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct CreateId : IEquatable<CreateId>
{
public UInt128 Raw;
public bool IsNull => Raw.IsNull;
public bool IsValid => !IsNull && (Raw.High & 0xC0) == 0x80;
public CreateId(byte[] data)
{
Raw = new UInt128(data);
}
public static bool operator ==(CreateId x, CreateId y)
{
return x.Equals(y);
}
public static bool operator !=(CreateId x, CreateId y)
{
return !x.Equals(y);
}
public override bool Equals(object obj)
{
return obj is CreateId createId && Equals(createId);
}
public bool Equals(CreateId cmpObj)
{
// Nintendo additionally check that the CreatorId is valid before doing the actual comparison.
return IsValid && Raw == cmpObj.Raw;
}
public override int GetHashCode()
{
return Raw.GetHashCode();
}
}
}

View file

@ -0,0 +1,197 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)]
struct DefaultMii
{
public const int Size = 0xD8;
public int FacelineType;
public int FacelineColorVer3;
public int FacelineWrinkle;
public int FacelineMake;
public int HairType;
public int HairColorVer3;
public int HairFlip;
public int EyeType;
public int EyeColorVer3;
public int EyeScale;
public int EyeAspect;
public int EyeRotate;
public int EyeX;
public int EyeY;
public int EyebrowType;
public int EyebrowColorVer3;
public int EyebrowScale;
public int EyebrowAspect;
public int EyebrowRotate;
public int EyebrowX;
public int EyebrowY;
public int NoseType;
public int NoseScale;
public int NoseY;
public int MouthType;
public int MouthColorVer3;
public int MouthScale;
public int MouthAspect;
public int MouthY;
public int MustacheType;
public int BeardType;
public int BeardColorVer3;
public int MustacheScale;
public int MustacheY;
public int GlassType;
public int GlassColorVer3;
public int GlassScale;
public int GlassY;
public int MoleType;
public int MoleScale;
public int MoleX;
public int MoleY;
public int Height;
public int Build;
public int Gender;
public int FavoriteColor;
public int RegionMove;
public int FontRegion;
public int Type;
private byte _nicknameFirstByte;
public Span<byte> NicknameStorage => MemoryMarshal.CreateSpan(ref _nicknameFirstByte, 20);
public Nickname Nickname
{
get => Nickname.FromBytes(NicknameStorage);
set => value.Raw.Slice(0, 20).CopyTo(NicknameStorage);
}
public static ReadOnlySpan<DefaultMii> Table => MemoryMarshal.Cast<byte, DefaultMii>(TableRawArray);
// The first 2 Mii in the default table are used as base for Male/Female in editor but not exposed via IPC.
public static int TableLength => _fromIndex.Length;
private static readonly int[] _fromIndex = new int[] { 2, 3, 4, 5, 6, 7 };
public static DefaultMii GetDefaultMii(uint index)
{
return Table[_fromIndex[index]];
}
#region "Raw Table Array"
private static ReadOnlySpan<byte> TableRawArray => new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
#endregion
}
}

View file

@ -0,0 +1,69 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum EyeType : byte
{
Normal,
NormalLash,
WhiteLash,
WhiteNoBottom,
OvalAngledWhite,
AngryWhite,
DotLashType1,
Line,
DotLine,
OvalWhite,
RoundedWhite,
NormalShadow,
CircleWhite,
Circle,
CircleWhiteStroke,
NormalOvalNoBottom,
NormalOvalLarge,
NormalRoundedNoBottom,
SmallLash,
Small,
TwoSmall,
NormalLongLash,
WhiteTwoLashes,
WhiteThreeLashes,
DotAngry,
DotAngled,
Oval,
SmallWhite,
WhiteAngledNoBottom,
WhiteAngledNoLeft,
SmallWhiteTwoLashes,
LeafWhiteLash,
WhiteLargeNoBottom,
Dot,
DotLashType2,
DotThreeLashes,
WhiteOvalTop,
WhiteOvalBottom,
WhiteOvalBottomFlat,
WhiteOvalTwoLashes,
WhiteOvalThreeLashes,
WhiteOvalNoBottomTwoLashes,
DotWhite,
WhiteOvalTopFlat,
WhiteThinLeaf,
StarThreeLashes,
LineTwoLashes,
CrowsFeet,
WhiteNoBottomFlat,
WhiteNoBottomRounded,
WhiteSmallBottomLine,
WhiteNoBottomLash,
WhiteNoPartialBottomLash,
WhiteOvalBottomLine,
WhiteNoBottomLashTopLine,
WhiteNoPartialBottomTwoLashes,
NormalTopLine,
WhiteOvalLash,
RoundTired,
WhiteLarge,
Min = 0,
Max = 59
}
}

View file

@ -0,0 +1,33 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum EyebrowType : byte
{
FlatAngledLarge,
LowArchRoundedThin,
SoftAngledLarge,
MediumArchRoundedThin,
RoundedMedium,
LowArchMedium,
RoundedThin,
UpThin,
MediumArchRoundedMedium,
RoundedLarge,
UpLarge,
FlatAngledLargeInverted,
MediumArchFlat,
AngledThin,
HorizontalLarge,
HighArchFlat,
Flat,
MediumArchLarge,
LowArchThin,
RoundedThinInverted,
HighArchLarge,
Hairy,
Dotted,
None,
Min = 0,
Max = 23
}
}

View file

@ -0,0 +1,19 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum FacelineColor : byte
{
Beige,
WarmBeige,
Natural,
Honey,
Chestnut,
Porcelain,
Ivory,
WarmIvory,
Almond,
Espresso,
Min = 0,
Max = 9
}
}

View file

@ -0,0 +1,21 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum FacelineMake : byte
{
None,
CheekPorcelain,
CheekNatural,
EyeShadowBlue,
CheekBlushPorcelain,
CheekBlushNatural,
CheekPorcelainEyeShadowBlue,
CheekPorcelainEyeShadowNatural,
CheekBlushPorcelainEyeShadowEspresso,
Freckles,
LionsManeBeard,
StubbleBeard,
Min = 0,
Max = 11
}
}

View file

@ -0,0 +1,21 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum FacelineType : byte
{
Sharp,
Rounded,
SharpRounded,
SharpRoundedSmall,
Large,
LargeRounded,
SharpSmall,
Flat,
Bump,
Angular,
FlatRounded,
AngularSmall,
Min = 0,
Max = 11
}
}

View file

@ -0,0 +1,21 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum FacelineWrinkle : byte
{
None,
TearTroughs,
FacialPain,
Cheeks,
Folds,
UnderTheEyes,
SplitChin,
Chin,
BrowDroop,
MouthFrown,
CrowsFeet,
FoldsCrowsFrown,
Min = 0,
Max = 11
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum FontRegion : byte
{
Standard,
China,
Korea,
Taiwan
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum Gender : byte
{
Male,
Female,
All,
Min = 0,
Max = 1
}
}

View file

@ -0,0 +1,29 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum GlassType : byte
{
None,
Oval,
Wayfarer,
Rectangle,
TopRimless,
Rounded,
Oversized,
CatEye,
Square,
BottomRimless,
SemiOpaqueRounded,
SemiOpaqueCatEye,
SemiOpaqueOval,
SemiOpaqueRectangle,
SemiOpaqueAviator,
OpaqueRounded,
OpaqueCatEye,
OpaqueOval,
OpaqueRectangle,
OpaqueAviator,
Min = 0,
Max = 19
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum HairFlip : byte
{
Left,
Right,
Min = 0,
Max = 1
}
}

View file

@ -0,0 +1,141 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum HairType : byte
{
NormalLong,
NormalShort,
NormalMedium,
NormalExtraLong,
NormalLongBottom,
NormalTwoPeaks,
PartingLong,
FrontLock,
PartingShort,
PartingExtraLongCurved,
PartingExtraLong,
PartingMiddleLong,
PartingSquared,
PartingLongBottom,
PeaksTop,
PeaksSquared,
PartingPeaks,
PeaksLongBottom,
Peaks,
PeaksRounded,
PeaksSide,
PeaksMedium,
PeaksLong,
PeaksRoundedLong,
PartingFrontPeaks,
PartingLongFront,
PartingLongRounded,
PartingFrontPeaksLong,
PartingExtraLongRounded,
LongRounded,
NormalUnknown1,
NormalUnknown2,
NormalUnknown3,
NormalUnknown4,
NormalUnknown5,
NormalUnknown6,
DreadLocks,
PlatedMats,
Caps,
Afro,
PlatedMatsLong,
Beanie,
Short,
ShortTopLongSide,
ShortUnknown1,
ShortUnknown2,
MilitaryParting,
Military,
ShortUnknown3,
ShortUnknown4,
ShortUnknown5,
ShortUnknown6,
NoneTop,
None,
LongUnknown1,
LongUnknown2,
LongUnknown3,
LongUnknown4,
LongUnknown5,
LongUnknown6,
LongUnknown7,
LongUnknown8,
LongUnknown9,
LongUnknown10,
LongUnknown11,
LongUnknown12,
LongUnknown13,
LongUnknown14,
LongUnknown15,
LongUnknown16,
LongUnknown17,
LongUnknown18,
LongUnknown19,
LongUnknown20,
LongUnknown21,
LongUnknown22,
LongUnknown23,
LongUnknown24,
LongUnknown25,
LongUnknown26,
LongUnknown27,
LongUnknown28,
LongUnknown29,
LongUnknown30,
LongUnknown31,
LongUnknown32,
LongUnknown33,
LongUnknown34,
LongUnknown35,
LongUnknown36,
LongUnknown37,
LongUnknown38,
LongUnknown39,
LongUnknown40,
LongUnknown41,
LongUnknown42,
LongUnknown43,
LongUnknown44,
LongUnknown45,
LongUnknown46,
LongUnknown47,
LongUnknown48,
LongUnknown49,
LongUnknown50,
LongUnknown51,
LongUnknown52,
LongUnknown53,
LongUnknown54,
LongUnknown55,
LongUnknown56,
LongUnknown57,
LongUnknown58,
LongUnknown59,
LongUnknown60,
LongUnknown61,
LongUnknown62,
LongUnknown63,
LongUnknown64,
LongUnknown65,
LongUnknown66,
TwoMediumFrontStrandsOneLongBackPonyTail,
TwoFrontStrandsLongBackPonyTail,
PartingFrontTwoLongBackPonyTails,
TwoFrontStrandsOneLongBackPonyTail,
LongBackPonyTail,
LongFrontTwoLongBackPonyTails,
StrandsTwoShortSidedPonyTails,
TwoMediumSidedPonyTails,
ShortFrontTwoBackPonyTails,
TwoShortSidedPonyTails,
TwoLongSidedPonyTails,
LongFrontTwoBackPonyTails,
Min = 0,
Max = 131
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
interface IElement
{
void SetFromStoreData(StoreData storeData);
void SetSource(Source source);
}
}

View file

@ -0,0 +1,15 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
interface IStoredData<T> : IElement, IEquatable<T> where T : notnull
{
byte Type { get; }
CreateId CreateId { get; }
ResultCode InvalidData { get; }
bool IsValid();
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum MoleType : byte
{
None,
OneDot,
Min = 0,
Max = 1
}
}

View file

@ -0,0 +1,45 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum MouthType : byte
{
Neutral,
NeutralLips,
Smile,
SmileStroke,
SmileTeeth,
LipsSmall,
LipsLarge,
Wave,
WaveAngrySmall,
NeutralStrokeLarge,
TeethSurprised,
LipsExtraLarge,
LipsUp,
NeutralDown,
Surprised,
TeethMiddle,
NeutralStroke,
LipsExtraSmall,
Malicious,
LipsDual,
NeutralComma,
NeutralUp,
TeethLarge,
WaveAngry,
LipsSexy,
SmileInverted,
LipsSexyOutline,
SmileRounded,
LipsTeeth,
NeutralOpen,
TeethRounded,
WaveAngrySmallInverted,
NeutralCommaInverted,
TeethFull,
SmileDownLine,
Kiss,
Min = 0,
Max = 35
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum MustacheType : byte
{
None,
Walrus,
Pencil,
Horseshoe,
Normal,
Toothbrush,
Min = 0,
Max = 5
}
}

View file

@ -0,0 +1,120 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 2, Size = SizeConst)]
struct Nickname : IEquatable<Nickname>
{
public const int CharCount = 10;
private const int SizeConst = (CharCount + 1) * 2;
private byte _storage;
public static Nickname Default => FromString("no name");
public static Nickname Question => FromString("???");
public Span<byte> Raw => MemoryMarshal.CreateSpan(ref _storage, SizeConst);
private ReadOnlySpan<ushort> Characters => MemoryMarshal.Cast<byte, ushort>(Raw);
private int GetEndCharacterIndex()
{
for (int i = 0; i < Characters.Length; i++)
{
if (Characters[i] == 0)
{
return i;
}
}
return -1;
}
public bool IsEmpty()
{
for (int i = 0; i < Characters.Length - 1; i++)
{
if (Characters[i] != 0)
{
return false;
}
}
return true;
}
public bool IsValid()
{
// Create a new unicode encoding instance with error checking enabled
UnicodeEncoding unicodeEncoding = new UnicodeEncoding(false, false, true);
try
{
unicodeEncoding.GetString(Raw);
return true;
}
catch (ArgumentException)
{
return false;
}
}
public bool IsValidForFontRegion(FontRegion fontRegion)
{
// TODO: We need to extract the character tables used here, for now just assume that if it's valid Unicode, it will be valid for any font.
return IsValid();
}
public override string ToString()
{
return Encoding.Unicode.GetString(Raw);
}
public static Nickname FromBytes(ReadOnlySpan<byte> data)
{
if (data.Length > SizeConst)
{
data = data.Slice(0, SizeConst);
}
Nickname result = new Nickname();
data.CopyTo(result.Raw);
return result;
}
public static Nickname FromString(string nickname)
{
return FromBytes(Encoding.Unicode.GetBytes(nickname));
}
public static bool operator ==(Nickname x, Nickname y)
{
return x.Equals(y);
}
public static bool operator !=(Nickname x, Nickname y)
{
return !x.Equals(y);
}
public override bool Equals(object obj)
{
return obj is Nickname nickname && Equals(nickname);
}
public bool Equals(Nickname cmpObj)
{
return Raw.SequenceEqual(cmpObj.Raw);
}
public override int GetHashCode()
{
return HashCode.Combine(Raw.ToArray());
}
}
}

View file

@ -0,0 +1,254 @@
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 8, Size = 0x1A98)]
struct NintendoFigurineDatabase
{
private const int DatabaseMagic = ('N' << 0) | ('F' << 8) | ('D' << 16) | ('B' << 24);
private const byte MaxMii = 100;
private const byte CurrentVersion = 1;
private const int FigurineArraySize = MaxMii * StoreData.Size;
private uint _magic;
private FigurineStorageStruct _figurineStorage;
private byte _version;
private byte _figurineCount;
private ushort _crc;
// Set to true to allow fixing database with invalid storedata device crc instead of deleting them.
private const bool AcceptInvalidDeviceCrc = true;
public int Length => _figurineCount;
[StructLayout(LayoutKind.Sequential, Size = FigurineArraySize)]
private struct FigurineStorageStruct { }
private Span<StoreData> Figurines => SpanHelpers.AsSpan<FigurineStorageStruct, StoreData>(ref _figurineStorage);
public StoreData Get(int index)
{
return Figurines[index];
}
public bool IsFull()
{
return Length >= MaxMii;
}
public bool GetIndexByCreatorId(out int index, CreateId createId)
{
for (int i = 0; i < Length; i++)
{
if (Figurines[i].CreateId == createId)
{
index = i;
return true;
}
}
index = -1;
return false;
}
public ResultCode Move(int newIndex, int oldIndex)
{
if (newIndex == oldIndex)
{
return ResultCode.NotUpdated;
}
StoreData tmp = Figurines[oldIndex];
int targetLength;
int sourceIndex;
int destinationIndex;
if (newIndex < oldIndex)
{
targetLength = oldIndex - newIndex;
sourceIndex = newIndex;
destinationIndex = newIndex + 1;
}
else
{
targetLength = newIndex - oldIndex;
sourceIndex = oldIndex + 1;
destinationIndex = oldIndex;
}
Figurines.Slice(sourceIndex, targetLength).CopyTo(Figurines.Slice(destinationIndex, targetLength));
Figurines[newIndex] = tmp;
UpdateCrc();
return ResultCode.Success;
}
public void Replace(int index, StoreData storeData)
{
Figurines[index] = storeData;
UpdateCrc();
}
public void Add(StoreData storeData)
{
Replace(_figurineCount++, storeData);
}
public void Delete(int index)
{
int newCount = _figurineCount - 1;
// If this isn't the only element in the list, move the data in it.
if (index < newCount)
{
int targetLength = newCount - index;
int sourceIndex = index + 1;
int destinationIndex = index;
Figurines.Slice(sourceIndex, targetLength).CopyTo(Figurines.Slice(destinationIndex, targetLength));
}
_figurineCount = (byte)newCount;
UpdateCrc();
}
public bool FixDatabase()
{
bool isBroken = false;
int i = 0;
while (i < Length)
{
ref StoreData figurine = ref Figurines[i];
if (!figurine.IsValid())
{
if (AcceptInvalidDeviceCrc && figurine.CoreData.IsValid() && figurine.IsValidDataCrc())
{
figurine.UpdateCrc();
}
else
{
Delete(i);
isBroken = true;
}
}
else
{
bool hasDuplicate = false;
CreateId createId = figurine.CreateId;
for (int j = 0; j < i; j++)
{
if (Figurines[j].CreateId == createId)
{
hasDuplicate = true;
break;
}
}
if (hasDuplicate)
{
Delete(i);
isBroken = true;
}
else
{
i++;
}
}
}
UpdateCrc();
return isBroken;
}
public ResultCode Verify()
{
if (_magic != DatabaseMagic)
{
return ResultCode.InvalidDatabaseMagic;
}
if (_version != CurrentVersion)
{
return ResultCode.InvalidDatabaseVersion;
}
if (!IsValidCrc())
{
return ResultCode.InvalidCrc;
}
if (_figurineCount > 100)
{
return ResultCode.InvalidDatabaseSize;
}
return ResultCode.Success;
}
public void Format()
{
_magic = DatabaseMagic;
_version = CurrentVersion;
_figurineCount = 0;
// Fill with empty data
Figurines.Fill(new StoreData());
UpdateCrc();
}
public void CorruptDatabase()
{
UpdateCrc();
_crc = (ushort)~_crc;
}
private void UpdateCrc()
{
_crc = CalculateCrc();
}
public bool IsValidCrc()
{
return _crc == CalculateCrc();
}
private ushort CalculateCrc()
{
return Helper.CalculateCrc16BE(AsSpanWithoutCrc());
}
public Span<byte> AsSpan()
{
return SpanHelpers.AsByteSpan(ref this);
}
public ReadOnlySpan<byte> AsReadOnlySpan()
{
return SpanHelpers.AsReadOnlyByteSpan(ref this);
}
private ReadOnlySpan<byte> AsSpanWithoutCrc()
{
return AsReadOnlySpan().Slice(0, Unsafe.SizeOf<NintendoFigurineDatabase>() - 2);
}
}
}

View file

@ -0,0 +1,27 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum NoseType : byte
{
Normal,
Rounded,
Dot,
Arrow,
Roman,
Triangle,
Button,
RoundedInverted,
Potato,
Grecian,
Snub,
Aquiline,
ArrowLeft,
RoundedLarge,
Hooked,
Fat,
Droopy,
ArrowLarge,
Min = 0,
Max = 17
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum Race : uint
{
Black,
White,
Asian,
All
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
enum Source : int
{
Database,
Default
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[Flags]
enum SourceFlag : int
{
Database = 1 << Source.Database,
Default = 1 << Source.Default,
All = Database | Default
}
}

View file

@ -0,0 +1,17 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 4)]
struct SpecialMiiKeyCode
{
private const uint SpecialMiiMagic = 0xA523B78F;
public uint RawValue;
public bool IsEnabledSpecialMii()
{
return RawValue == SpecialMiiMagic;
}
}
}

View file

@ -0,0 +1,232 @@
using LibHac.Common;
using Ryujinx.HLE.Utilities;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)]
struct StoreData : IStoredData<StoreData>
{
public const int Size = 0x44;
public CoreData CoreData;
private CreateId _createId;
public ushort DataCrc;
public ushort DeviceCrc;
public byte Type => CoreData.Type;
public CreateId CreateId => _createId;
public ResultCode InvalidData => ResultCode.InvalidStoreData;
private void UpdateDataCrc()
{
DataCrc = CalculateDataCrc();
}
private void UpdateDeviceCrc()
{
DeviceCrc = CalculateDeviceCrc();
}
public void UpdateCrc()
{
UpdateDataCrc();
UpdateDeviceCrc();
}
public bool IsSpecial()
{
return CoreData.Type == 1;
}
public bool IsValid()
{
return CoreData.IsValid() && IsValidDataCrc() && IsValidDeviceCrc();
}
public bool IsValidDataCrc()
{
return DataCrc == CalculateDataCrc();
}
public bool IsValidDeviceCrc()
{
return DeviceCrc == CalculateDeviceCrc();
}
private ushort CalculateDataCrc()
{
return Helper.CalculateCrc16BE(AsSpanWithoutCrc());
}
private ushort CalculateDeviceCrc()
{
UInt128 deviceId = Helper.GetDeviceId();
ushort deviceIdCrc16 = Helper.CalculateCrc16BE(SpanHelpers.AsByteSpan(ref deviceId));
return Helper.CalculateCrc16BE(AsSpanWithoutDeviceCrc(), deviceIdCrc16);
}
private ReadOnlySpan<byte> AsSpan()
{
return MemoryMarshal.AsBytes(SpanHelpers.CreateReadOnlySpan(ref this, 1));
}
private ReadOnlySpan<byte> AsSpanWithoutCrc()
{
return AsSpan().Slice(0, Size - 4);
}
private ReadOnlySpan<byte> AsSpanWithoutDeviceCrc()
{
return AsSpan().Slice(0, Size - 2);
}
public static StoreData BuildDefault(UtilityImpl utilImpl, uint index)
{
StoreData result = new StoreData
{
_createId = utilImpl.MakeCreateId()
};
CoreData coreData = new CoreData();
DefaultMii template = DefaultMii.GetDefaultMii(index);
coreData.SetDefault();
coreData.Nickname = template.Nickname;
coreData.FontRegion = (FontRegion)template.FontRegion;
coreData.FavoriteColor = (byte)template.FavoriteColor;
coreData.Gender = (Gender)template.Gender;
coreData.Height = (byte)template.Height;
coreData.Build = (byte)template.Build;
coreData.Type = (byte)template.Type;
coreData.RegionMove = (byte)template.RegionMove;
coreData.FacelineType = (FacelineType)template.FacelineType;
coreData.FacelineColor = (FacelineColor)Helper.Ver3FacelineColorTable[template.FacelineColorVer3];
coreData.FacelineWrinkle = (FacelineWrinkle)template.FacelineWrinkle;
coreData.FacelineMake = (FacelineMake)template.FacelineMake;
coreData.HairType = (HairType)template.HairType;
coreData.HairColor = (CommonColor)Helper.Ver3HairColorTable[template.HairColorVer3];
coreData.HairFlip = (HairFlip)template.HairFlip;
coreData.EyeType = (EyeType)template.EyeType;
coreData.EyeColor = (CommonColor)Helper.Ver3EyeColorTable[template.EyeColorVer3];
coreData.EyeScale = (byte)template.EyeScale;
coreData.EyeAspect = (byte)template.EyeAspect;
coreData.EyeRotate = (byte)template.EyeRotate;
coreData.EyeX = (byte)template.EyeX;
coreData.EyeY = (byte)template.EyeY;
coreData.EyebrowType = (EyebrowType)template.EyebrowType;
coreData.EyebrowColor = (CommonColor)Helper.Ver3HairColorTable[template.EyebrowColorVer3];
coreData.EyebrowScale = (byte)template.EyebrowScale;
coreData.EyebrowAspect = (byte)template.EyebrowAspect;
coreData.EyebrowRotate = (byte)template.EyebrowRotate;
coreData.EyebrowX = (byte)template.EyebrowX;
coreData.EyebrowY = (byte)template.EyebrowY;
coreData.NoseType = (NoseType)template.NoseType;
coreData.NoseScale = (byte)template.NoseScale;
coreData.NoseY = (byte)template.NoseY;
coreData.MouthType = (MouthType)template.MouthType;
coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[template.MouthColorVer3];
coreData.MouthScale = (byte)template.MouthScale;
coreData.MouthAspect = (byte)template.MouthAspect;
coreData.MouthY = (byte)template.MouthY;
coreData.BeardColor = (CommonColor)Helper.Ver3HairColorTable[template.BeardColorVer3];
coreData.BeardType = (BeardType)template.BeardType;
coreData.MustacheType = (MustacheType)template.MustacheType;
coreData.MustacheScale = (byte)template.MustacheScale;
coreData.MustacheY = (byte)template.MustacheY;
coreData.GlassType = (GlassType)template.GlassType;
coreData.GlassColor = (CommonColor)Helper.Ver3GlassColorTable[template.GlassColorVer3];
coreData.GlassScale = (byte)template.GlassScale;
coreData.GlassY = (byte)template.GlassY;
coreData.MoleType = (MoleType)template.MoleType;
coreData.MoleScale = (byte)template.MoleScale;
coreData.MoleX = (byte)template.MoleX;
coreData.MoleY = (byte)template.MoleY;
result.CoreData = coreData;
result.UpdateCrc();
return result;
}
public static StoreData BuildRandom(UtilityImpl utilImpl, Age age, Gender gender, Race race)
{
return BuildFromCoreData(utilImpl, CoreData.BuildRandom(utilImpl, age, gender, race));
}
public static StoreData BuildFromCoreData(UtilityImpl utilImpl, CoreData coreData)
{
StoreData result = new StoreData
{
CoreData = coreData,
_createId = utilImpl.MakeCreateId()
};
result.UpdateCrc();
return result;
}
public void SetFromStoreData(StoreData storeData)
{
this = storeData;
}
public void SetSource(Source source)
{
// Only implemented for Element variants.
}
public static bool operator ==(StoreData x, StoreData y)
{
return x.Equals(y);
}
public static bool operator !=(StoreData x, StoreData y)
{
return !x.Equals(y);
}
public override bool Equals(object obj)
{
return obj is StoreData storeData && Equals(storeData);
}
public bool Equals(StoreData cmpObj)
{
if (!cmpObj.IsValid())
{
return false;
}
bool result = true;
result &= CreateId == cmpObj.CreateId;
result &= CoreData == cmpObj.CoreData;
result &= DataCrc == cmpObj.DataCrc;
result &= DeviceCrc == cmpObj.DeviceCrc;
return result;
}
public override int GetHashCode()
{
HashCode hashCode = new HashCode();
hashCode.Add(CreateId);
hashCode.Add(CoreData);
hashCode.Add(DataCrc);
hashCode.Add(DeviceCrc);
return hashCode.ToHashCode();
}
}
}

View file

@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x48)]
struct StoreDataElement : IElement
{
public StoreData StoreData;
public Source Source;
public void SetFromStoreData(StoreData storeData)
{
StoreData = storeData;
}
public void SetSource(Source source)
{
Source = source;
}
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Mii.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)]
struct Ver3StoreData
{
public const int Size = 0x60;
private byte _storage;
public Span<byte> Storage => MemoryMarshal.CreateSpan(ref _storage, Size);
// TODO: define all getters/setters
}
}

View file

@ -0,0 +1,67 @@
using Ryujinx.HLE.HOS.Services.Mii.Types;
using Ryujinx.HLE.HOS.Services.Time;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using System;
namespace Ryujinx.HLE.HOS.Services.Mii
{
class UtilityImpl
{
private uint _x;
private uint _y;
private uint _z;
private uint _w;
public UtilityImpl()
{
_x = 123456789;
_y = 362436069;
TimeSpanType time = TimeManager.Instance.TickBasedSteadyClock.GetCurrentRawTimePoint(null);
_w = (uint)(time.NanoSeconds & uint.MaxValue);
_z = (uint)((time.NanoSeconds >> 32) & uint.MaxValue);
}
private uint GetRandom()
{
uint t = (_x ^ (_x << 11));
_x = _y;
_y = _z;
_z = _w;
_w = (_w ^ (_w >> 19)) ^ (t ^ (t >> 8));
return _w;
}
public int GetRandom(int end)
{
return (int)GetRandom((uint)end);
}
public uint GetRandom(uint end)
{
uint random = GetRandom();
return random - random / end * end;
}
public uint GetRandom(uint start, uint end)
{
uint random = GetRandom();
return random - random / (1 - start + end) * (1 - start + end) + start;
}
public int GetRandom(int start, int end)
{
return (int)GetRandom((uint)start, (uint)end);
}
public CreateId MakeCreateId()
{
return new CreateId(Guid.NewGuid().ToByteArray());
}
}
}

View file

@ -1,9 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Sdb.Mii
{
[Service("mii:e")]
[Service("mii:u")]
class IStaticService : IpcService
{
public IStaticService(ServiceCtx context) { }
}
}

View file

@ -5,6 +5,8 @@ namespace Ryujinx.HLE.HOS.SystemState
{
public class SystemStateMgr
{
public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000");
internal static string[] LanguageCodes = new string[]
{
"ja",
@ -53,10 +55,8 @@ namespace Ryujinx.HLE.HOS.SystemState
Account = new AccountUtils();
UserId defaultUid = new UserId("00000000000000010000000000000000");
Account.AddUser(defaultUid, "Player");
Account.OpenUser(defaultUid);
Account.AddUser(DefaultUserId, "Player");
Account.OpenUser(DefaultUserId);
}
public void SetLanguage(SystemLanguage language)