Skip to content

Commit e9bea7e

Browse files
committed
fix currency object not being byte-accurate to vanilla
1 parent 9704198 commit e9bea7e

3 files changed

Lines changed: 79 additions & 23 deletions

File tree

Dat/FileParsing/SawyerStreamUtils.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ static uint32_t ComputeChecksum(ReadOnlySpan<byte> data, uint32_t seed)
2323
}
2424

2525
const uint32_t objectChecksumMagic = 0xF369A75B;
26-
var checksum = ComputeChecksum(headerFlagByte, objectChecksumMagic); // 1295935387
27-
checksum = ComputeChecksum(name, checksum); // 2991070967
28-
checksum = ComputeChecksum(data, checksum); // 1733551639
26+
var checksum = ComputeChecksum(headerFlagByte, objectChecksumMagic);
27+
checksum = ComputeChecksum(name, checksum);
28+
checksum = ComputeChecksum(data, checksum);
2929
return checksum;
3030
}
3131

Dat/FileParsing/SawyerStreamWriter.cs

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Definitions.ObjectModels.Graphics;
99
using Definitions.ObjectModels.Objects.Sound;
1010
using Definitions.ObjectModels.Types;
11+
using System.Collections.Immutable;
1112
using System.ComponentModel.DataAnnotations;
1213
using System.Text;
1314

@@ -559,18 +560,70 @@ public static void WriteVariableStream(Stream ms, LocoObject obj)
559560

560561
public static void WriteStringTable(Stream ms, StringTable table)
561562
{
563+
const bool wantVanillaExact = false;
564+
if (wantVanillaExact)
565+
{
566+
WriteVanillaStringTable(ms, table);
567+
}
568+
else
569+
{
570+
WriteSimpleStringTable(ms, table);
571+
}
572+
}
573+
574+
// this is a byte-perfect method for recreating the vanilla objects (specifically Currency)
575+
// however it has unnecessary bytes when strings are missing in certain languages
576+
// but other strings in the table have values for that language
577+
static void WriteVanillaStringTable(Stream ms, StringTable table)
578+
{
579+
//var languagesUsed = table.Table
580+
// .Select(x => x.Value)
581+
// .Select(x => x
582+
// .Where(str => !string.IsNullOrEmpty(str.Value))
583+
// .Select(str => str.Key))
584+
// .SelectMany(x => x)
585+
// .Distinct()
586+
// .ToImmutableHashSet();
587+
588+
List<LanguageId> languagesUsed = [LanguageId.English_UK, LanguageId.English_US];
589+
562590
foreach (var ste in table.Table)
563591
{
564-
foreach (var language in ste.Value.Where(str => !string.IsNullOrEmpty(str.Value))) // skip strings with empty content
592+
foreach (var language in ste.Value)
565593
{
566-
ms.WriteByte((byte)language.Key);
594+
// skip strings with empty content
595+
if (!string.IsNullOrEmpty(language.Value))
596+
{
597+
ms.WriteByte((uint8_t)language.Key);
598+
ms.Write(Encoding.Latin1.GetBytes(language.Value));
599+
ms.WriteByte((uint8_t)'\0');
600+
}
601+
else if (languagesUsed.Contains(language.Key)) // but if the string is empty, AND its language has other valid strings, vanilla objects actually wrote these useless bytes
602+
{
603+
// vanilla currency objects do this!!!!
604+
ms.WriteByte((uint8_t)language.Key);
605+
ms.WriteByte((uint8_t)'\0');
606+
}
607+
}
608+
609+
ms.WriteByte(LocoConstants.Terminator);
610+
}
611+
}
567612

568-
var strBytes = Encoding.Latin1.GetBytes(language.Value);
569-
ms.Write(strBytes, 0, strBytes.Length);
570-
ms.WriteByte((byte)'\0');
613+
// this is a simplified and more-correct way to write the string table to bytes
614+
// it is perfectly compatible with vanilla loco, but doesn't produce byte-accurate objects
615+
static void WriteSimpleStringTable(Stream ms, StringTable table)
616+
{
617+
foreach (var ste in table.Table)
618+
{
619+
foreach (var language in ste.Value.Where(x => !string.IsNullOrEmpty(x.Value))) // skip strings with empty content
620+
{
621+
ms.WriteByte((uint8_t)language.Key);
622+
ms.Write(Encoding.Latin1.GetBytes(language.Value));
623+
ms.WriteByte((uint8_t)'\0');
571624
}
572625

573-
ms.WriteByte(0xff);
626+
ms.WriteByte(LocoConstants.Terminator);
574627
}
575628
}
576629

@@ -593,7 +646,7 @@ public static void WriteImageTable(Stream ms, List<GraphicsElement> graphicsElem
593646
Offset = g1Element.Flags.HasFlag(DatG1ElementFlags.DuplicatePrevious) ? previousOffset : offsetBytesIntoImageData,
594647
};
595648

596-
offsetBytesIntoImageData += (uint)newElement.ImageData.Length;
649+
offsetBytesIntoImageData += (uint32_t)newElement.ImageData.Length;
597650
encoded.Add(newElement);
598651

599652
previousOffset = newElement.Offset;

Tests/LoadSaveTests.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,22 @@ static void LoadSaveGenericTest<T>(string filename, Action<LocoObject, T> assert
7878
assertFunc(obj2, struc2);
7979
var bytes2 = SawyerStreamWriter.WriteLocoObject(datInfo2.S5Header.Name, obj2.ObjectType, datInfo2.S5Header.ObjectSource.Convert(datInfo2.S5Header.Name, datInfo2.S5Header.Checksum), datInfo2.ObjectHeader.Encoding, logger, obj2, true).ToArray();
8080

81-
// grab headers first
82-
var s5Header1 = S5Header.Read(bytes1.AsSpan()[0..S5Header.StructLength]);
83-
var s5Header2 = S5Header.Read(bytes2.AsSpan()[0..S5Header.StructLength]);
84-
AssertS5Headers(s5Header1, s5Header2);
85-
86-
var objHeader1 = ObjectHeader.Read(bytes1.AsSpan()[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]);
87-
var objHeader2 = ObjectHeader.Read(bytes2.AsSpan()[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]);
88-
AssertObjHeaders(objHeader1, objHeader2);
89-
90-
// then grab object bytes
91-
var bytes1ObjArr = bytes1[21..].ToArray();
92-
var bytes2ObjArr = bytes2[21..].ToArray();
93-
Assert.That(bytes1ObjArr.ToArray(), Is.EqualTo(bytes2ObjArr.ToArray()));
81+
using (Assert.EnterMultipleScope())
82+
{
83+
// grab headers first
84+
var s5Header1 = S5Header.Read(bytes1.AsSpan()[0..S5Header.StructLength]);
85+
var s5Header2 = S5Header.Read(bytes2.AsSpan()[0..S5Header.StructLength]);
86+
AssertS5Headers(s5Header1, s5Header2);
87+
88+
var objHeader1 = ObjectHeader.Read(bytes1.AsSpan()[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]);
89+
var objHeader2 = ObjectHeader.Read(bytes2.AsSpan()[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]);
90+
AssertObjHeaders(objHeader1, objHeader2);
91+
92+
// then grab object bytes
93+
var bytes1ObjArr = bytes1[21..].ToArray();
94+
var bytes2ObjArr = bytes2[21..].ToArray();
95+
Assert.That(bytes1ObjArr.ToArray(), Is.EqualTo(bytes2ObjArr.ToArray()));
96+
}
9497
}
9598

9699
static void AssertS5Headers(S5Header expected, S5Header actual)

0 commit comments

Comments
 (0)