diff --git a/src/BizHawk.Client.EmuHawk/config/NES/NESSyncSettingsForm.cs b/src/BizHawk.Client.EmuHawk/config/NES/NESSyncSettingsForm.cs index 76a8d0bc78..ce689314a9 100644 --- a/src/BizHawk.Client.EmuHawk/config/NES/NESSyncSettingsForm.cs +++ b/src/BizHawk.Client.EmuHawk/config/NES/NESSyncSettingsForm.cs @@ -49,10 +49,8 @@ namespace BizHawk.Client.EmuHawk RegionComboBox.Items.AddRange(Enum.GetNames(typeof(NES.NESSyncSettings.Region)).Cast().ToArray()); RegionComboBox.SelectedItem = Enum.GetName(typeof(NES.NESSyncSettings.Region), _syncSettings.RegionOverride); - if (_syncSettings.InitialWRamStatePattern is { Count: > 0 } initWRAMPattern) - { - RamPatternOverrideBox.Text = initWRAMPattern.BytesToHexString(); - } + var initWRAMPattern = _syncSettings.InitialWRamStatePattern; + if (initWRAMPattern.Length is not 0) RamPatternOverrideBox.Text = initWRAMPattern.BytesToHexString(); } private void CancelBtn_Click(object sender, EventArgs e) @@ -69,14 +67,14 @@ namespace BizHawk.Client.EmuHawk typeof(NES.NESSyncSettings.Region), (string)RegionComboBox.SelectedItem); - var oldRam = _syncSettings.InitialWRamStatePattern ?? new List(); + var oldRam = _syncSettings.InitialWRamStatePattern; if (!string.IsNullOrWhiteSpace(RamPatternOverrideBox.Text)) { _syncSettings.InitialWRamStatePattern = Enumerable.Range(0, RamPatternOverrideBox.Text.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(RamPatternOverrideBox.Text.Substring(x, 2), 16)) - .ToList(); + .ToArray(); } else { @@ -85,7 +83,7 @@ namespace BizHawk.Client.EmuHawk bool changed = (_dataTableDictionary != null && _dataTableDictionary.WasModified) || old != _syncSettings.RegionOverride || - !(oldRam.SequenceEqual(_syncSettings.InitialWRamStatePattern ?? new List())); + !oldRam.SequenceEqual(_syncSettings.InitialWRamStatePattern); DialogResult = DialogResult.OK; if (changed) diff --git a/src/BizHawk.Emulation.Common/U8ArrayAsNormalJSONListConverter.cs b/src/BizHawk.Emulation.Common/U8ArrayAsNormalJSONListConverter.cs new file mode 100644 index 0000000000..4ab496833e --- /dev/null +++ b/src/BizHawk.Emulation.Common/U8ArrayAsNormalJSONListConverter.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace BizHawk.Emulation.Common +{ + /// seems unnecessary, but suggested by official docs so sure why not + public sealed class U8ArrayAsNormalJSONListResolver : DefaultContractResolver + { + public static readonly U8ArrayAsNormalJSONListResolver INSTANCE = new(); + + protected override JsonContract CreateContract(Type objectType) + { + var contract = base.CreateContract(objectType); + if (objectType == typeof(byte[])) contract.Converter = U8ArrayAsNormalJSONListConverter.INSTANCE; + return contract; + } + } + + /// based on this SO answer + public sealed class U8ArrayAsNormalJSONListConverter : JsonConverter + { + public static readonly U8ArrayAsNormalJSONListConverter INSTANCE = new(); + + public override byte[]? ReadJson(JsonReader reader, Type objectType, byte[]? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType is JsonToken.Null) return null; + if (reader.TokenType is not JsonToken.StartArray) throw new Exception($"Unexpected token when reading bytes: expected {nameof(JsonToken.StartArray)}, got {reader.TokenType}"); + List list = new(); + while (reader.Read()) switch (reader.TokenType) + { + case JsonToken.Integer: + list.Add(reader.Value switch + { + byte b => b, + long l and >= byte.MinValue and <= byte.MaxValue => unchecked((byte) l), + var o => throw new Exception($"Integer literal outside u8 range: {o}") + }); + continue; + case JsonToken.EndArray: + return list.ToArray(); + case JsonToken.Comment: + continue; + default: + throw new Exception($"Unexpected token when reading bytes: {reader.TokenType}"); + } + throw new Exception("Unexpected end when reading bytes"); + } + + public override void WriteJson(JsonWriter writer, byte[]? value, JsonSerializer serializer) + { + if (value is null) + { + writer.WriteNull(); + return; + } + writer.WriteStartArray(); + for (var i = 0; i < value.Length; i++) writer.WriteValue(value[i]); + writer.WriteEndArray(); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index 522a8385a6..7c3e1ad0ac 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -227,11 +227,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES // apu has some specific power up bahaviour that we will emulate here apu.NESHardReset(); - if (SyncSettings.InitialWRamStatePattern != null && SyncSettings.InitialWRamStatePattern.Any()) + var initWRAMPattern = SyncSettings.InitialWRamStatePattern; + if (initWRAMPattern.Length is not 0) { for (int i = 0; i < 0x800; i++) { - ram[i] = SyncSettings.InitialWRamStatePattern[i % SyncSettings.InitialWRamStatePattern.Count]; + ram[i] = initWRAMPattern[i % initWRAMPattern.Length]; } } else diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.ISettable.cs index 937f39b590..f6cbfbbc2c 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.ISettable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.ISettable.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using BizHawk.Common; using BizHawk.Emulation.Common; +using Newtonsoft.Json; + namespace BizHawk.Emulation.Cores.Nintendo.NES { public partial class NES : ISettable @@ -59,7 +62,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public NESControlSettings Controls = new NESControlSettings(); - public List InitialWRamStatePattern = null; + [JsonConverter(typeof(U8ArrayAsNormalJSONListConverter))] // this preserves the old behaviour of e,g, 0x1234ABCD --> [18,52,171,205]; omitting it will use base64 e.g. "EjSrzQ==" + [JsonProperty(PropertyName = nameof(InitialWRamStatePattern))] + private byte[]/*?*/ _initialWRamStatePattern = null; + + [JsonIgnore] + public byte[] InitialWRamStatePattern + { + get + { + if (_initialWRamStatePattern is null) return Array.Empty(); + if (_initialWRamStatePattern.Length is 0) + { + _initialWRamStatePattern = null; + return Array.Empty(); + } + return _initialWRamStatePattern; + } + set => _initialWRamStatePattern = value.Length is 0 ? null : value; + } public NESSyncSettings Clone() { @@ -75,7 +96,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES return !(Util.DictionaryEqual(x.BoardProperties, y.BoardProperties) && x.RegionOverride == y.RegionOverride && !NESControlSettings.NeedsReboot(x.Controls, y.Controls) - && ((x.InitialWRamStatePattern ?? new List()).SequenceEqual(y.InitialWRamStatePattern ?? new List())) + && x.InitialWRamStatePattern.SequenceEqual(y.InitialWRamStatePattern) && x.VSDipswitches.Equals(y.VSDipswitches)); }