Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
83c5fce
progress
LeftofZen Aug 29, 2025
5d73f8a
Merge branch 'master' into imageTableGroups2
LeftofZen Aug 29, 2025
abaaa58
building example
LeftofZen Aug 29, 2025
52e7c7c
Merge branch 'master' into imageTableGroups2
LeftofZen Aug 29, 2025
c904777
progress
LeftofZen Aug 29, 2025
05dfeb3
building variations
LeftofZen Aug 31, 2025
f2c5040
code cleanup
LeftofZen Aug 31, 2025
6b129c7
work on buildingcomponents
LeftofZen Sep 3, 2025
4e00c22
Merge remote-tracking branch 'origin/master' into imageTableGroups2
LeftofZen Sep 3, 2025
4861c37
Merge remote-tracking branch 'origin/master' into imageTableGroups2
LeftofZen Sep 4, 2025
607f98b
building components view working
LeftofZen Sep 4, 2025
a4514e6
flyout-vs-menuflyout
LeftofZen Sep 5, 2025
0fe5229
add colour swatch effect
LeftofZen Sep 5, 2025
9116501
move around converters
LeftofZen Sep 6, 2025
3663e9f
Merge branch 'imageTableGroups2' of https://github.com/OpenLoco/Objec…
LeftofZen Sep 6, 2025
f34ad87
Merge remote-tracking branch 'origin/master' into imageTableGroups2
LeftofZen Sep 6, 2025
cf7ad75
fix bad merge
LeftofZen Sep 6, 2025
5a2baa3
progress
LeftofZen Sep 12, 2025
90d8628
Merge remote-tracking branch 'origin/master' into imageTableGroups2
LeftofZen Sep 12, 2025
7d48de2
fix merge
LeftofZen Sep 12, 2025
8ba96cf
progress
LeftofZen Sep 16, 2025
d3ec840
move image naming out of object definitions
LeftofZen Sep 16, 2025
127d6ff
add basic g1.dat groups
LeftofZen Sep 16, 2025
8460b40
add more image groups
LeftofZen Sep 16, 2025
0a8179e
fixing unit test failures
LeftofZen Sep 17, 2025
c127f40
add proper validation
LeftofZen Sep 18, 2025
31a8b0d
ensure unit tests run for gog and steam g1.dat
LeftofZen Sep 18, 2025
02b463b
add costindex query
LeftofZen Sep 18, 2025
e3e3354
Merge remote-tracking branch 'origin' into imageTableGroups2
LeftofZen Sep 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ dotnet_diagnostic.IDE0078.severity = suggestion
dotnet_diagnostic.IDE0080.severity = suggestion
dotnet_diagnostic.IDE0082.severity = suggestion
dotnet_diagnostic.IDE0083.severity = suggestion
dotnet_diagnostic.IDE0090.severity = suggestion
dotnet_diagnostic.IDE0090.severity = error
dotnet_diagnostic.IDE0100.severity = suggestion
dotnet_diagnostic.IDE0110.severity = suggestion
dotnet_diagnostic.IDE0120.severity = suggestion
Expand Down
3 changes: 1 addition & 2 deletions Dat/FileParsing/LocoBinaryReader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Dat.Converters;
using Dat.Types;
using Definitions.ObjectModels.Objects.Building;
using Definitions.ObjectModels.Objects.Common;
using Definitions.ObjectModels.Objects.Shared;
using Definitions.ObjectModels.Objects.Sound;
using Definitions.ObjectModels.Objects.Vehicle;
Expand Down Expand Up @@ -331,7 +331,6 @@ public GearboxMotorSound ReadGearboxMotorSound()
SpeedFrequencyFactor = ReadByte(),
};


public CargoOffset[][][] ReadCargoOffsets()
{
const int rotationSize = 4;
Expand Down
2 changes: 1 addition & 1 deletion Dat/FileParsing/LocoBinaryWriter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using Common;
using Dat.Converters;
using Dat.Types;
using Definitions.ObjectModels.Objects.Building;
using Definitions.ObjectModels.Objects.Shared;
using Definitions.ObjectModels.Objects.Common;
using Definitions.ObjectModels.Objects.Sound;
using Definitions.ObjectModels.Objects.Vehicle;
using Definitions.ObjectModels.Types;
Expand Down
17 changes: 12 additions & 5 deletions Dat/FileParsing/SawyerStreamReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Definitions.ObjectModels;
using Definitions.ObjectModels.Objects.Sound;
using Definitions.ObjectModels.Types;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace Dat.FileParsing;
Expand Down Expand Up @@ -137,9 +138,9 @@ static void ValidateLocoStruct(S5Header s5Header, ILocoStruct locoStruct, ILogge
}
}

if (!locoStruct.Validate())
foreach (var failedValidation in locoStruct.Validate(new ValidationContext(locoStruct)))
{
warnings.Add($"\"{s5Header.Name}\" failed validation");
warnings.Add($"\"{s5Header.Name}\" failed validation: {failedValidation}");
}

if (warnings.Count != 0)
Expand Down Expand Up @@ -252,6 +253,8 @@ public static (G1Header Header, List<GraphicsElement> Table) ReadImageTable(Loco
var imageData = br.ReadToEnd();
g1Header.ImageData = [.. imageData];

var graphicsElements = new List<GraphicsElement>();

// set image data
for (var i = 0; i < g1Header.NumEntries; ++i)
{
Expand All @@ -272,15 +275,19 @@ public static (G1Header Header, List<GraphicsElement> Table) ReadImageTable(Loco
currElement.ImageData = [.. imageData[(int)currElement.Offset..(int)nextOffset]];
}

// if rleCompressed, uncompress it, except if the duplicate-previous flag is also set - by the current code here, the previous
// image (which was also compressed) is now uncompressed, so we don't need do double-uncompress it.
// if rleCompressed, decompress it, except if the duplicate-previous flag is also set - by the current code here, the previous
// image (which was also compressed) is now decompressed, so we don't need do double-decompress it.
if (currElement.Flags.HasFlag(DatG1ElementFlags.IsRLECompressed) && !currElement.Flags.HasFlag(DatG1ElementFlags.DuplicatePrevious))
{
currElement.ImageData = DecodeRLEImageData(currElement);
}

var ge = currElement.Convert();
ge.Name = DefaultImageTableNameProvider.GetImageName(i);
graphicsElements.Add(ge);
}

return (g1Header, g1Element32s.Select(x => x.Convert()).ToList());
return (g1Header, graphicsElements);
}

static uint GetNextNonDuplicateOffset(List<DatG1Element32> g1Element32s, int i, uint imageDateLength)
Expand Down
8 changes: 5 additions & 3 deletions Dat/FileParsing/SawyerStreamWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Definitions.ObjectModels;
using Definitions.ObjectModels.Objects.Sound;
using Definitions.ObjectModels.Types;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace Dat.FileParsing;
Expand Down Expand Up @@ -490,9 +491,10 @@ public static void WriteLocoObject(Stream stream, LocoObject obj)

public static MemoryStream WriteLocoObject(string objName, ObjectType objectType, ObjectSource objectSource, SawyerEncoding encoding, ILogger logger, LocoObject obj, bool allowWritingAsVanilla)
{
if (!obj.Object.Validate())
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateObject(obj.Object, new ValidationContext(obj.Object), validationResults))
{
throw new ArgumentException($"{objName} was invalid", nameof(obj));
throw new ArgumentException($"{objName} was invalid: {string.Join(", ", validationResults.Select(r => r.ErrorMessage))}", nameof(obj));
}

using var rawObjStream = new MemoryStream();
Expand Down Expand Up @@ -615,7 +617,7 @@ public static void SaveG1(string filename, G1Dat g1)
{
using (var fs = File.OpenWrite(filename))
{
WriteImageTable(fs, g1.GraphicsElements);
WriteImageTable(fs, g1.ImageTable.GraphicsElements);
}
}
}
39 changes: 22 additions & 17 deletions Dat/Loaders/AirportObjectLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ public static class Constants

internal static class StructSizes
{
public const int Dat = 0xBA;
public const int BuildingPartAnimation = 0x02;
public const int AirportBuilding = 0x04;
public const int MovementNode = 0x08;
public const int MovementEdge = 0x0C;
}

public static ObjectType ObjectType => ObjectType.Airport;
public static DatObjectType DatObjectType => DatObjectType.Airport;

public static LocoObject Load(Stream stream)
{
var initialStreamPosition = stream.Position;

using (var br = new LocoBinaryReader(stream))
{
var model = new AirportObject();
var stringTable = new StringTable();
var imageTable = new List<GraphicsElement>();

// fixed
br.SkipStringId(); // Name offset, not part of object definition
Expand Down Expand Up @@ -64,26 +64,29 @@ public static LocoObject Load(Stream stream)
model.var_B6 = br.ReadBytes(0xBA - 0xB6);

// sanity check
ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position));
ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position));

// string table
stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Airport), null);
var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null);

// variable
LoadVariable(br, model, numBuildingParts, numBuildingVariations, numMovementNodes, numMovementEdges);

// image table
imageTable = SawyerStreamReader.ReadImageTable(br).Table;
var imageList = SawyerStreamReader.ReadImageTable(br).Table;

// define groups
var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList);

return new LocoObject(ObjectType.Airport, model, stringTable, imageTable);
return new LocoObject(ObjectType, model, stringTable, imageTable);
}
}

private static void LoadVariable(LocoBinaryReader br, AirportObject model, int numBuildingParts, int numBuildingVariations, byte numMovementNodes, byte numMovementEdges)
{
model.BuildingHeights = br.ReadBuildingHeights(numBuildingParts);
model.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts);
model.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations);
model.BuildingComponents.BuildingHeights = br.ReadBuildingHeights(numBuildingParts);
model.BuildingComponents.BuildingAnimations = br.ReadBuildingAnimations(numBuildingParts);
model.BuildingComponents.BuildingVariations = br.ReadBuildingVariations(numBuildingVariations);

// building positions
while (br.PeekByte() != LocoConstants.Terminator)
Expand All @@ -97,6 +100,7 @@ private static void LoadVariable(LocoBinaryReader br, AirportObject model, int n
};
model.BuildingPositions.Add(ab);
}

br.SkipTerminator();

// movement nodes
Expand Down Expand Up @@ -146,8 +150,8 @@ public static void Save(Stream stream, LocoObject obj)
bw.WriteEmptyImageId(); // Image, not part of object definition
bw.WriteEmptyImageId(); // Image offset, not part of object definition
bw.Write(model.AllowedPlaneTypes);
bw.Write((uint8_t)model.BuildingHeights.Count);
bw.Write((uint8_t)model.BuildingVariations.Count);
bw.Write((uint8_t)model.BuildingComponents.BuildingHeights.Count);
bw.Write((uint8_t)model.BuildingComponents.BuildingVariations.Count);
bw.WriteEmptyPointer(); // BuildingHeights
bw.WriteEmptyPointer(); // BuildingAnimations
bw.WriteEmptyPointer(Constants.BuildingVariationCount); // BuildingVariations
Expand All @@ -166,7 +170,7 @@ public static void Save(Stream stream, LocoObject obj)
bw.Write(model.var_B6);

// sanity check
ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position));
ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position));

// string table
SawyerStreamWriter.WriteStringTable(stream, obj.StringTable);
Expand All @@ -175,15 +179,15 @@ public static void Save(Stream stream, LocoObject obj)
SaveVariable(bw, model);

// image table
SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements);
SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements);
}
}

private static void SaveVariable(LocoBinaryWriter bw, AirportObject model)
{
bw.Write(model.BuildingHeights);
bw.Write(model.BuildingAnimations);
bw.Write(model.BuildingVariations);
bw.Write(model.BuildingComponents.BuildingHeights);
bw.Write(model.BuildingComponents.BuildingAnimations);
bw.Write(model.BuildingComponents.BuildingVariations);

// positions
foreach (var x in model.BuildingPositions)
Expand All @@ -193,6 +197,7 @@ private static void SaveVariable(LocoBinaryWriter bw, AirportObject model)
bw.Write(x.X);
bw.Write(x.Y);
}

bw.WriteTerminator();

// movement nodes
Expand Down
24 changes: 11 additions & 13 deletions Dat/Loaders/BridgeObjectLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ public static class Constants
public const int MaxNumRoadMods = 7;
}

public static class StructSizes
{
public const int Dat = 0x2C;
}
public static ObjectType ObjectType => ObjectType.Bridge;
public static DatObjectType DatObjectType => DatObjectType.Bridge;

public static LocoObject Load(Stream stream)
{
Expand All @@ -28,8 +26,6 @@ public static LocoObject Load(Stream stream)
using (var br = new LocoBinaryReader(stream))
{
var model = new BridgeObject();
var stringTable = new StringTable();
var imageTable = new List<GraphicsElement>();

// fixed
br.SkipStringId(); // Name offset, not part of object definition
Expand All @@ -54,19 +50,22 @@ public static LocoObject Load(Stream stream)
model.DesignedYear = br.ReadUInt16();

// sanity check
ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position));
ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position));

// string table
stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType.Bridge), null);
var stringTable = SawyerStreamReader.ReadStringTableStream(stream, ObjectAttributes.StringTable(DatObjectType), null);

// variable
model.CompatibleTrackObjects = br.ReadS5HeaderList(compatibleTrackCount);
model.CompatibleRoadObjects = br.ReadS5HeaderList(compatibleRoadCount);

// image table
imageTable = SawyerStreamReader.ReadImageTable(br).Table;
var imageList = SawyerStreamReader.ReadImageTable(br).Table;

return new LocoObject(ObjectType.Bridge, model, stringTable, imageTable);
// define groups
var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList);

return new LocoObject(ObjectType, model, stringTable, imageTable);
}
}

Expand Down Expand Up @@ -99,7 +98,7 @@ public static void Save(Stream stream, LocoObject obj)
bw.Write(model.DesignedYear);

// sanity check
ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + StructSizes.Dat, nameof(stream.Position));
ArgumentOutOfRangeException.ThrowIfNotEqual(stream.Position, initialStreamPosition + ObjectAttributes.StructSize(DatObjectType), nameof(stream.Position));

// string table
SawyerStreamWriter.WriteStringTable(stream, obj.StringTable);
Expand All @@ -109,7 +108,7 @@ public static void Save(Stream stream, LocoObject obj)
bw.WriteS5HeaderList(model.CompatibleRoadObjects);

// image table
SawyerStreamWriter.WriteImageTable(stream, obj.GraphicsElements);
SawyerStreamWriter.WriteImageTable(stream, obj.ImageTable.GraphicsElements);
}
}

Expand Down Expand Up @@ -150,7 +149,6 @@ internal enum DatBridgeObjectFlags : uint8_t
None = 0,
HasRoof = 1 << 0,
}

}

static class BridgeFlagsConverter
Expand Down
Loading