BizHawk/BizHawk.Emulation.Cores/Consoles/Nintendo/NDS/MelonDS_Settable.cs

200 lines
5.0 KiB
C#

using System;
using System.ComponentModel;
using System.Text;
using System.Runtime.InteropServices;
using BizHawk.Emulation.Common;
using Newtonsoft.Json;
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
unsafe partial class MelonDS : ISettable<MelonDS.MelonSettings, MelonDS.MelonSyncSettings>
{
private MelonSettings _settings = new MelonSettings();
public MelonSettings GetSettings() => _settings.Clone();
public MelonSyncSettings GetSyncSettings()
{
var ret = new MelonSyncSettings();
fixed (byte* ptr = ret.UserSettings)
{
if (!GetUserSettings(ptr))
{
return null;
}
}
ret.BootToFirmware = !GetDirectBoot();
ret.TimeAtBoot = GetTimeAtBoot();
return ret;
}
public PutSettingsDirtyBits PutSettings(MelonSettings o)
{
bool screenChanged = false;
if (o != null)
{
screenChanged |= _settings.ScaleFactor != o.ScaleFactor;
screenChanged |= _settings.ScreenGap != o.ScreenGap;
screenChanged |= _settings.ScreenLayout != o.ScreenLayout;
screenChanged |= _settings.ScreenRotation != o.ScreenRotation;
}
_settings = o ?? new MelonSettings();
SetScaleFactor(_settings.ScaleFactor);
return screenChanged ? PutSettingsDirtyBits.None : PutSettingsDirtyBits.None;
}
public PutSettingsDirtyBits PutSyncSettings(MelonSyncSettings o)
{
if (o == null)
{
o = new MelonSyncSettings();
SetUserSettings(null);
}
else
{
fixed (byte* ptr = o.UserSettings)
SetUserSettings(ptr);
}
SetDirectBoot(!o.BootToFirmware);
SetTimeAtBoot(o.TimeAtBoot);
// At present, no sync settings can be modified without requiring a reboot.
return PutSettingsDirtyBits.RebootCore;
}
[DllImport(dllPath)]
private static extern bool GetUserSettings(byte* dst);
[DllImport(dllPath)]
private static extern int GetUserSettingsLength();
private static readonly int UserSettingsLength = GetUserSettingsLength();
[DllImport(dllPath)]
private static extern void SetUserSettings(byte* src);
[DllImport(dllPath)]
private static extern bool GetDirectBoot();
[DllImport(dllPath)]
private static extern void SetDirectBoot(bool value);
[DllImport(dllPath)]
private static extern void SetTimeAtBoot(uint value);
[DllImport(dllPath)]
private static extern uint GetTimeAtBoot();
[DllImport(dllPath)]
private static extern uint GetScaleFactor();
[DllImport(dllPath)]
private static extern void SetScaleFactor(uint value);
public enum ScreenLayoutKind
{
Vertical, Horizontal, Top, Bottom
}
public enum ScreenRotationKind
{
Rotate0, Rotate90, Rotate180, Rotate270
}
public class MelonSettings
{
public MelonSettings Clone() => (MelonSettings)MemberwiseClone();
[DisplayName("Screen Layout")]
[Description("Adjusts the layout of the screens")]
public ScreenLayoutKind ScreenLayout { get; set; } = ScreenLayoutKind.Vertical;
[DisplayName("Rotation")]
[Description("Adjusts the orientation of the screens")]
public ScreenRotationKind ScreenRotation { get; set; } = ScreenRotationKind.Rotate0;
[DisplayName("Screen Gap")]
public int ScreenGap { get; set; }
[DisplayName("Scale Factor")]
public uint ScaleFactor { get; set; } = 1;
}
public class MelonSyncSettings
{
public MelonSyncSettings()
{
UserSettings = new byte[UserSettingsLength];
}
public MelonSyncSettings Clone() => (MelonSyncSettings)MemberwiseClone();
public bool BootToFirmware { get; set; }
public uint TimeAtBoot { get; set; } = 946684800; // 2000-01-01 00:00:00 (earliest date possible on a DS)
[JsonProperty]
internal byte[] UserSettings;
[JsonIgnore]
public byte FavoriteColor
{
get => UserSettings[2];
set => UserSettings[2] = value;
}
[JsonIgnore]
public byte BirthdayMonth
{
get => UserSettings[3];
set => UserSettings[3] = value;
}
[JsonIgnore]
public byte BirthdayDay
{
get => UserSettings[4];
set => UserSettings[4] = value;
}
private const int MaxNicknameLength = 10;
[JsonIgnore]
public string Nickname
{
get
{
fixed (byte* ptr = UserSettings)
return Encoding.Unicode.GetString(ptr + 6, NicknameLength * 2);
}
set
{
if (value.Length > MaxNicknameLength) value = value.Substring(0, MaxNicknameLength);
byte[] nick = new byte[MaxNicknameLength * 2 + 2];
// I do not know how an actual NDS would handle characters that require more than 2 bytes to encode.
// They can't be input normally, so I will ignore attempts to set a nickname that uses them.
if (Encoding.Unicode.GetBytes(value, 0, value.Length, nick, 0) != value.Length * 2)
{
return;
}
// The extra 2 bytes on the end will overwrite nickname length, which is set immediately after
nick.CopyTo(UserSettings, 6);
UserSettings[0x1A] = (byte)value.Length;
}
}
[JsonIgnore]
public short NicknameLength => UserSettings[0x1A];
[JsonIgnore]
public long RtcOffset
{
get => BitConverter.ToInt64(UserSettings, 0x68);
set => BitConverter.GetBytes(value).CopyTo(UserSettings, 0x68);
}
}
}
}