From bb265e65c6f0aeeca00eab8f15cdb92ac196a4d6 Mon Sep 17 00:00:00 2001 From: gmipf Date: Fri, 16 Oct 2026 12:17:43 +0200 Subject: [PATCH] Format refresh and freq with the invariant culture The DAT writers and the filter formatted Refresh and Freq with the current culture, so a German or French system wrote "123,450000". SabreTools.IO parses these values with the invariant culture. Co-Authored-By: Claude Opus 5.5 --- .../FilterObjectTests.cs | 29 +++++++++++++++++++ SabreTools.Metadata.Filter/FilterObject.cs | 5 ++-- .../ClrMameProTests.cs | 20 +++++++++++++ .../ListxmlTests.cs | 20 +++++++++++++ .../M1Tests.cs | 20 +++++++++++++ .../MessTests.cs | 20 +++++++++++++ .../ClrMamePro.cs | 3 +- SabreTools.Serialization.Writers/Listxml.cs | 5 ++-- SabreTools.Serialization.Writers/M1.cs | 5 ++-- SabreTools.Serialization.Writers/Mess.cs | 5 ++-- 10 files changed, 123 insertions(+), 9 deletions(-) diff --git a/SabreTools.Metadata.Filter.Test/FilterObjectTests.cs b/SabreTools.Metadata.Filter.Test/FilterObjectTests.cs index be7555096..6aeea9c4f 100644 --- a/SabreTools.Metadata.Filter.Test/FilterObjectTests.cs +++ b/SabreTools.Metadata.Filter.Test/FilterObjectTests.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using SabreTools.Data.Models.Metadata; using SabreTools.Hashing; using Xunit; @@ -1022,6 +1023,20 @@ public void Matches_Display(string itemField, string value) Assert.True(actual); } + [Theory] + [InlineData("en-US")] + [InlineData("de-DE")] + [InlineData("fr-FR")] + public void Matches_DisplayRefresh_CurrentCultureIgnored(string culture) + { + CultureInfo.CurrentCulture = new CultureInfo(culture); + var filter = new FilterObject("display.refresh", "123.45", Operation.Equals); + Display obj = new Display { Refresh = 123.45 }; + + bool actual = filter.Matches(obj); + Assert.True(actual); + } + #endregion #region Driver @@ -2085,6 +2100,20 @@ public void Matches_Video(string itemField, string value) Assert.True(actual); } + [Theory] + [InlineData("en-US")] + [InlineData("de-DE")] + [InlineData("fr-FR")] + public void Matches_VideoRefresh_CurrentCultureIgnored(string culture) + { + CultureInfo.CurrentCulture = new CultureInfo(culture); + var filter = new FilterObject("video.refresh", "123.45", Operation.Equals); + Video obj = new Video { Refresh = 123.45 }; + + bool actual = filter.Matches(obj); + Assert.True(actual); + } + #endregion } } diff --git a/SabreTools.Metadata.Filter/FilterObject.cs b/SabreTools.Metadata.Filter/FilterObject.cs index 86d1d59d2..0c941d98c 100644 --- a/SabreTools.Metadata.Filter/FilterObject.cs +++ b/SabreTools.Metadata.Filter/FilterObject.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Text.RegularExpressions; using SabreTools.Data.Extensions; using SabreTools.Data.Models.Metadata; @@ -1134,7 +1135,7 @@ private static bool GetCheckValue(Display obj, string fieldName, out string? che return true; case "refresh": case "freq": - checkValue = obj.Refresh?.ToString(); + checkValue = obj.Refresh?.ToString(CultureInfo.InvariantCulture); return true; case "rotate": case "orientation": @@ -2512,7 +2513,7 @@ private static bool GetCheckValue(Video obj, string fieldName, out string? check return true; case "refresh": case "freq": - checkValue = obj.Refresh?.ToString(); + checkValue = obj.Refresh?.ToString(CultureInfo.InvariantCulture); return true; case "displaytype": case "screen": diff --git a/SabreTools.Serialization.Readers.Test/ClrMameProTests.cs b/SabreTools.Serialization.Readers.Test/ClrMameProTests.cs index dda42561f..7736132ec 100644 --- a/SabreTools.Serialization.Readers.Test/ClrMameProTests.cs +++ b/SabreTools.Serialization.Readers.Test/ClrMameProTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Linq; using Xunit; @@ -171,6 +172,25 @@ public void RoundTripMachineWithoutQuotesTest() Validate(newGame); } + [Theory] + [InlineData("en-US")] + [InlineData("de-DE")] + [InlineData("fr-FR")] + public void SerializeFreq_CurrentCultureIgnored(string culture) + { + CultureInfo.CurrentCulture = new CultureInfo(culture); + var serializer = new Writers.ClrMamePro(); + + // Serialize to stream + Stream? actual = serializer.SerializeStream(Build(game: false)); + Assert.NotNull(actual); + + // Validate the freq value + string dat = new StreamReader(actual).ReadToEnd(); + Assert.Contains("123.450000", dat); + Assert.DoesNotContain("123,45", dat); + } + /// /// Build model for serialization and deserialization /// diff --git a/SabreTools.Serialization.Readers.Test/ListxmlTests.cs b/SabreTools.Serialization.Readers.Test/ListxmlTests.cs index d525afa35..483b408ef 100644 --- a/SabreTools.Serialization.Readers.Test/ListxmlTests.cs +++ b/SabreTools.Serialization.Readers.Test/ListxmlTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Linq; using Xunit; @@ -125,6 +126,25 @@ public void RoundTripMachineTest() Validate(newGame); } + [Theory] + [InlineData("en-US")] + [InlineData("de-DE")] + [InlineData("fr-FR")] + public void SerializeRefresh_CurrentCultureIgnored(string culture) + { + CultureInfo.CurrentCulture = new CultureInfo(culture); + var serializer = new Writers.Listxml(); + + // Serialize to stream + Stream? actual = serializer.SerializeStream(Build(game: false)); + Assert.NotNull(actual); + + // Validate the refresh values + string xml = new StreamReader(actual).ReadToEnd(); + Assert.Contains("refresh=\"123.450000\"", xml); + Assert.DoesNotContain("123,45", xml); + } + /// /// Build model for serialization and deserialization /// diff --git a/SabreTools.Serialization.Readers.Test/M1Tests.cs b/SabreTools.Serialization.Readers.Test/M1Tests.cs index 79a82b57d..9f8d7a576 100644 --- a/SabreTools.Serialization.Readers.Test/M1Tests.cs +++ b/SabreTools.Serialization.Readers.Test/M1Tests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Linq; using Xunit; @@ -121,6 +122,25 @@ public void RoundTripMachineTest() Validate(newGame); } + [Theory] + [InlineData("en-US")] + [InlineData("de-DE")] + [InlineData("fr-FR")] + public void SerializeRefresh_CurrentCultureIgnored(string culture) + { + CultureInfo.CurrentCulture = new CultureInfo(culture); + var serializer = new Writers.M1(); + + // Serialize to stream + Stream? actual = serializer.SerializeStream(Build(game: false)); + Assert.NotNull(actual); + + // Validate the refresh values + string xml = new StreamReader(actual).ReadToEnd(); + Assert.Contains("refresh=\"123.450000\"", xml); + Assert.DoesNotContain("123,45", xml); + } + /// /// Build model for serialization and deserialization /// diff --git a/SabreTools.Serialization.Readers.Test/MessTests.cs b/SabreTools.Serialization.Readers.Test/MessTests.cs index 5473df942..51d6f8331 100644 --- a/SabreTools.Serialization.Readers.Test/MessTests.cs +++ b/SabreTools.Serialization.Readers.Test/MessTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Linq; using Xunit; @@ -121,6 +122,25 @@ public void RoundTripMachineTest() Validate(newGame); } + [Theory] + [InlineData("en-US")] + [InlineData("de-DE")] + [InlineData("fr-FR")] + public void SerializeRefresh_CurrentCultureIgnored(string culture) + { + CultureInfo.CurrentCulture = new CultureInfo(culture); + var serializer = new Writers.Mess(); + + // Serialize to stream + Stream? actual = serializer.SerializeStream(Build(game: false)); + Assert.NotNull(actual); + + // Validate the refresh values + string xml = new StreamReader(actual).ReadToEnd(); + Assert.Contains("refresh=\"123.450000\"", xml); + Assert.DoesNotContain("123,45", xml); + } + /// /// Build model for serialization and deserialization /// diff --git a/SabreTools.Serialization.Writers/ClrMamePro.cs b/SabreTools.Serialization.Writers/ClrMamePro.cs index d82bdb969..d10a9f4e0 100644 --- a/SabreTools.Serialization.Writers/ClrMamePro.cs +++ b/SabreTools.Serialization.Writers/ClrMamePro.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Text; using SabreTools.Data.Extensions; @@ -419,7 +420,7 @@ private static void WriteVideos(Video[]? videos, Writer writer) writer.WriteOptionalAttributeString("y", video.Y.ToString()); writer.WriteOptionalAttributeString("aspectx", video.AspectX.ToString()); writer.WriteOptionalAttributeString("aspecty", video.AspectY.ToString()); - writer.WriteOptionalAttributeString("freq", video.Freq?.ToString("0.000000")); + writer.WriteOptionalAttributeString("freq", video.Freq?.ToString("0.000000", CultureInfo.InvariantCulture)); writer.WriteEndElement(); // video } } diff --git a/SabreTools.Serialization.Writers/Listxml.cs b/SabreTools.Serialization.Writers/Listxml.cs index 89b638e1f..fcd83d736 100644 --- a/SabreTools.Serialization.Writers/Listxml.cs +++ b/SabreTools.Serialization.Writers/Listxml.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -395,7 +396,7 @@ private static void WriteDisplay(Display obj, XmlTextWriter writer) writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo()); writer.WriteOptionalAttributeString("width", obj.Width?.ToString()); writer.WriteOptionalAttributeString("height", obj.Height?.ToString()); - writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000")); + writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture)); writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString()); writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString()); writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString()); @@ -843,7 +844,7 @@ private static void WriteVideo(Video obj, XmlTextWriter writer) writer.WriteOptionalAttributeString("height", obj.Height.ToString()); writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString()); writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString()); - writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000")); + writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture)); writer.WriteEndElement(); } diff --git a/SabreTools.Serialization.Writers/M1.cs b/SabreTools.Serialization.Writers/M1.cs index af84c695d..76879db9c 100644 --- a/SabreTools.Serialization.Writers/M1.cs +++ b/SabreTools.Serialization.Writers/M1.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -393,7 +394,7 @@ private static void WriteDisplay(Display obj, XmlTextWriter writer) writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo()); writer.WriteOptionalAttributeString("width", obj.Width?.ToString()); writer.WriteOptionalAttributeString("height", obj.Height?.ToString()); - writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000")); + writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture)); writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString()); writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString()); writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString()); @@ -841,7 +842,7 @@ private static void WriteVideo(Video obj, XmlTextWriter writer) writer.WriteOptionalAttributeString("height", obj.Height.ToString()); writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString()); writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString()); - writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000")); + writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture)); writer.WriteEndElement(); } diff --git a/SabreTools.Serialization.Writers/Mess.cs b/SabreTools.Serialization.Writers/Mess.cs index ada680f84..0d3678b73 100644 --- a/SabreTools.Serialization.Writers/Mess.cs +++ b/SabreTools.Serialization.Writers/Mess.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -393,7 +394,7 @@ private static void WriteDisplay(Display obj, XmlTextWriter writer) writer.WriteOptionalAttributeString("flipx", obj.FlipX.FromYesNo()); writer.WriteOptionalAttributeString("width", obj.Width?.ToString()); writer.WriteOptionalAttributeString("height", obj.Height?.ToString()); - writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000")); + writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture)); writer.WriteOptionalAttributeString("pixclock", obj.PixClock?.ToString()); writer.WriteOptionalAttributeString("htotal", obj.HTotal?.ToString()); writer.WriteOptionalAttributeString("hbend", obj.HBEnd?.ToString()); @@ -841,7 +842,7 @@ private static void WriteVideo(Video obj, XmlTextWriter writer) writer.WriteOptionalAttributeString("height", obj.Height.ToString()); writer.WriteOptionalAttributeString("aspectx", obj.AspectX.ToString()); writer.WriteOptionalAttributeString("aspecty", obj.AspectY.ToString()); - writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000")); + writer.WriteRequiredAttributeString("refresh", obj.Refresh?.ToString("0.000000", CultureInfo.InvariantCulture)); writer.WriteEndElement(); }