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 { 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); } } } }