From 2883eabc332a0223618745c8b4157e8f6418e347 Mon Sep 17 00:00:00 2001 From: frKieran <172341841+frKieran@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:32:04 -0400 Subject: [PATCH] support m64 extended format version 4 and keep unmodelled header bytes Mupen 1.5.0-3 (extended version 4) stores cpu_cf and rcp_lag_factor as doubles at 0x030 and 0x038. ToBytes wrote that area as zero padding, so the roundtrip check in LoadBytes threw on every movie recorded with 1.5.0-3. Read and write both fields, and write back the bytes this class doesn't model from the loaded header instead of zeros, so later additions to the format don't break loading. Fixes #66 Co-Authored-By: Claude Opus 5.5 --- STROOP/M64/M64Header.cs | 51 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/STROOP/M64/M64Header.cs b/STROOP/M64/M64Header.cs index 670816cf5..c807521d6 100644 --- a/STROOP/M64/M64Header.cs +++ b/STROOP/M64/M64Header.cs @@ -156,6 +156,41 @@ public uint NumRerecordsHi } } + // 030 8-byte double: CPU counter factor used when recording (extended version 4+) + private double _cpuCf; + + [CategoryAttribute("​​​​​Main"), DisplayName("CPU Counter Factor")] + public double CpuCf + { + get => _cpuCf; + set + { + _cpuCf = value; + NotifyChange(); + } + } + + // 038 8-byte double: RCP lag factor used when recording, 0 if RCP lag emulation was off (extended version 4+) + private double _rcpLagFactor; + + [CategoryAttribute("​​​​​Main"), DisplayName("RCP Lag Factor")] + public double RcpLagFactor + { + get => _rcpLagFactor; + set + { + _rcpLagFactor = value; + NotifyChange(); + } + } + + // The loaded header, so that bytes this class doesn't model (reserved space, fields added by newer + // extended versions) are written back unchanged instead of zeroed + private byte[] _loadedBytes; + + private byte[] UnmodelledBytes(int offset, int length) => + _loadedBytes?.Skip(offset).Take(length).ToArray() ?? new byte[length]; + // 0C4 32-byte ASCII string: internal name of ROM used when recording, directly from ROM private string _romName; @@ -516,7 +551,8 @@ public void LoadBytes(byte[] bytes) { if (bytes.Length != M64Config.HeaderSize) throw new ArgumentOutOfRangeException(); - Signature = BitConverter.ToUInt32(bytes, 0x000); + _loadedBytes = bytes.ToArray(); + Signature =BitConverter.ToUInt32(bytes, 0x000); VersionNumber = BitConverter.ToUInt32(bytes, 0x004); Uid = BitConverter.ToInt32(bytes, 0x008); NumVis = BitConverter.ToInt32(bytes, 0x00C); @@ -546,6 +582,8 @@ public void LoadBytes(byte[] bytes) AuthorshipTag = BitConverter.ToUInt32(bytes, 0x024); BruteforceExtraData = BitConverter.ToUInt32(bytes, 0x028); NumRerecordsHi = BitConverter.ToUInt32(bytes, 0x02C); + CpuCf = BitConverter.ToDouble(bytes, 0x030); + RcpLagFactor = BitConverter.ToDouble(bytes, 0x038); RomName = Encoding.ASCII.GetString(bytes, 0x0C4, 32).TrimEnd('\0'); Crc32 = BitConverter.ToUInt32(bytes, 0x0E4); CountryCode = BitConverter.ToUInt16(bytes, 0x0E8); @@ -574,16 +612,18 @@ public byte[] ToBytes() bytes.AddRange(TypeUtilities.GetBytes(ExtendedFlags)); bytes.AddRange(TypeUtilities.GetBytes(NumInputs)); bytes.AddRange(TypeUtilities.GetBytes((ushort)MovieStartType)); - bytes.AddRange(new byte[2]); + bytes.AddRange(UnmodelledBytes(0x01E, 2)); bytes.AddRange(TypeUtilities.GetBytes(GetControllerFlagsValue())); bytes.AddRange(TypeUtilities.GetBytes(AuthorshipTag)); bytes.AddRange(TypeUtilities.GetBytes(BruteforceExtraData)); bytes.AddRange(TypeUtilities.GetBytes(NumRerecordsHi)); - bytes.AddRange(new byte[148]); + bytes.AddRange(TypeUtilities.GetBytes(CpuCf)); + bytes.AddRange(TypeUtilities.GetBytes(RcpLagFactor)); + bytes.AddRange(UnmodelledBytes(0x040, 132)); bytes.AddRange(TypeUtilities.GetBytes(RomName, 32, Encoding.ASCII)); bytes.AddRange(TypeUtilities.GetBytes(Crc32)); bytes.AddRange(TypeUtilities.GetBytes(CountryCode)); - bytes.AddRange(new byte[56]); + bytes.AddRange(UnmodelledBytes(0x0EA, 56)); bytes.AddRange(TypeUtilities.GetBytes(VideoPlugin, 64, Encoding.ASCII)); bytes.AddRange(TypeUtilities.GetBytes(SoundPlugin, 64, Encoding.ASCII)); bytes.AddRange(TypeUtilities.GetBytes(InputPlugin, 64, Encoding.ASCII)); @@ -656,6 +696,9 @@ public void Clear() AuthorshipTag = 0; BruteforceExtraData = 0; NumRerecordsHi = 0; + CpuCf = 0; + RcpLagFactor = 0; + _loadedBytes = null; VideoPlugin = null; SoundPlugin = null; InputPlugin = null;